]>
git.saurik.com Git - apt.git/blob - methods/http.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: http.cc,v 1.26 1999/02/15 00:26:55 jgg Exp $
4 /* ######################################################################
6 HTTP Aquire Method - This is the HTTP aquire method for APT.
8 It uses HTTP/1.1 and many of the fancy options there-in, such as
9 pipelining, range, if-range and so on. It accepts on the command line
10 a list of url destination pairs and writes to stdout the status of the
11 operation as defined in the APT method spec.
13 It is based on a doubly buffered select loop. All the requests are
14 fed into a single output buffer that is constantly fed out the
15 socket. This provides ideal pipelining as in many cases all of the
16 requests will fit into a single packet. The input socket is buffered
17 the same way and fed into the fd for the file.
19 This double buffering provides fairly substantial transfer rates,
20 compared to wget the http method is about 4% faster. Most importantly,
21 when HTTP is compared with FTP as a protocol the speed difference is
22 huge. In tests over the internet from two sites to llug (via ATM) this
23 program got 230k/s sustained http transfer rates. FTP on the other
24 hand topped out at 170k/s. That combined with the time to setup the
25 FTP connection makes HTTP a vastly superior protocol.
27 ##################################################################### */
29 // Include Files /*{{{*/
30 #include <apt-pkg/fileutl.h>
31 #include <apt-pkg/acquire-method.h>
32 #include <apt-pkg/error.h>
33 #include <apt-pkg/md5.h>
44 #include <netinet/in.h>
45 #include <sys/socket.h>
46 #include <arpa/inet.h>
52 string
HttpMethod::FailFile
;
53 int HttpMethod::FailFd
= -1;
54 time_t HttpMethod::FailTime
= 0;
55 unsigned long PipelineDepth
= 10;
56 unsigned long TimeOut
= 120;
58 // CircleBuf::CircleBuf - Circular input buffer /*{{{*/
59 // ---------------------------------------------------------------------
61 CircleBuf::CircleBuf(unsigned long Size
) : Size(Size
), MD5(0)
63 Buf
= new unsigned char[Size
];
67 // CircleBuf::Reset - Reset to the default state /*{{{*/
68 // ---------------------------------------------------------------------
70 void CircleBuf::Reset()
75 MaxGet
= (unsigned int)-1;
80 MD5
= new MD5Summation
;
84 // CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/
85 // ---------------------------------------------------------------------
86 /* This fills up the buffer with as much data as is in the FD, assuming it
88 bool CircleBuf::Read(int Fd
)
92 // Woops, buffer is full
93 if (InP
- OutP
== Size
)
96 // Write the buffer segment
98 Res
= read(Fd
,Buf
+ (InP%Size
),LeftRead());
110 gettimeofday(&Start
,0);
115 // CircleBuf::Read - Put the string into the buffer /*{{{*/
116 // ---------------------------------------------------------------------
117 /* This will hold the string in and fill the buffer with it as it empties */
118 bool CircleBuf::Read(string Data
)
125 // CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/
126 // ---------------------------------------------------------------------
128 void CircleBuf::FillOut()
130 if (OutQueue
.empty() == true)
134 // Woops, buffer is full
135 if (InP
- OutP
== Size
)
138 // Write the buffer segment
139 unsigned long Sz
= LeftRead();
140 if (OutQueue
.length() - StrPos
< Sz
)
141 Sz
= OutQueue
.length() - StrPos
;
142 memcpy(Buf
+ (InP%Size
),OutQueue
.begin() + StrPos
,Sz
);
147 if (OutQueue
.length() == StrPos
)
156 // CircleBuf::Write - Write from the buffer into a FD /*{{{*/
157 // ---------------------------------------------------------------------
158 /* This empties the buffer into the FD. */
159 bool CircleBuf::Write(int Fd
)
165 // Woops, buffer is empty
172 // Write the buffer segment
174 Res
= write(Fd
,Buf
+ (OutP%Size
),LeftWrite());
187 MD5
->Add(Buf
+ (OutP%Size
),Res
);
193 // CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/
194 // ---------------------------------------------------------------------
195 /* This copies till the first empty line */
196 bool CircleBuf::WriteTillEl(string
&Data
,bool Single
)
198 // We cheat and assume it is unneeded to have more than one buffer load
199 for (unsigned long I
= OutP
; I
< InP
; I
++)
201 if (Buf
[I%Size
] != '\n')
203 for (I
++; I
< InP
&& Buf
[I%Size
] == '\r'; I
++);
207 if (Buf
[I%Size
] != '\n')
209 for (I
++; I
< InP
&& Buf
[I%Size
] == '\r'; I
++);
218 unsigned long Sz
= LeftWrite();
221 if (I
- OutP
< LeftWrite())
223 Data
+= string((char *)(Buf
+ (OutP%Size
)),Sz
);
231 // CircleBuf::Stats - Print out stats information /*{{{*/
232 // ---------------------------------------------------------------------
234 void CircleBuf::Stats()
240 gettimeofday(&Stop
,0);
241 /* float Diff = Stop.tv_sec - Start.tv_sec +
242 (float)(Stop.tv_usec - Start.tv_usec)/1000000;
243 clog << "Got " << InP << " in " << Diff << " at " << InP/Diff << endl;*/
247 // ServerState::ServerState - Constructor /*{{{*/
248 // ---------------------------------------------------------------------
250 ServerState::ServerState(URI Srv
,HttpMethod
*Owner
) : Owner(Owner
),
251 In(64*1024), Out(4*1024),
257 // ServerState::Open - Open a connection to the server /*{{{*/
258 // ---------------------------------------------------------------------
259 /* This opens a connection to the server. */
262 bool ServerState::Open()
264 // Use the already open connection if possible.
272 // Determine the proxy setting
273 if (getenv("http_proxy") == 0)
275 string DefProxy
= _config
->Find("Acquire::http::Proxy");
276 string SpecificProxy
= _config
->Find("Acquire::http::Proxy::" + ServerName
.Host
);
277 if (SpecificProxy
.empty() == false)
279 if (SpecificProxy
== "DIRECT")
282 Proxy
= SpecificProxy
;
288 Proxy
= getenv("http_proxy");
290 // Determine what host and port to use based on the proxy settings
293 if (Proxy
.empty() == true)
295 if (ServerName
.Port
!= 0)
296 Port
= ServerName
.Port
;
297 Host
= ServerName
.Host
;
306 /* We used a cached address record.. Yes this is against the spec but
307 the way we have setup our rotating dns suggests that this is more
309 if (LastHost
!= Host
)
311 Owner
->Status("Connecting to %s",Host
.c_str());
314 hostent
*Addr
= gethostbyname(Host
.c_str());
315 if (Addr
== 0 || Addr
->h_addr_list
[0] == 0)
316 return _error
->Error("Could not resolve '%s'",Host
.c_str());
318 LastHostA
= *(in_addr
*)(Addr
->h_addr_list
[0]);
321 Owner
->Status("Connecting to %s (%s)",Host
.c_str(),inet_ntoa(LastHostA
));
324 if ((ServerFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
325 return _error
->Errno("socket","Could not create a socket");
327 // Connect to the server
328 struct sockaddr_in server
;
329 server
.sin_family
= AF_INET
;
330 server
.sin_port
= htons(Port
);
331 server
.sin_addr
= LastHostA
;
332 SetNonBlock(ServerFd
,true);
333 if (connect(ServerFd
,(sockaddr
*)&server
,sizeof(server
)) < 0 &&
334 errno
!= EINPROGRESS
)
335 return _error
->Errno("socket","Could not create a socket");
337 /* This implements a timeout for connect by opening the connection
341 FD_SET(ServerFd
,&wfds
);
346 if ((Res
= select(ServerFd
+1,0,&wfds
,0,&tv
)) < 0)
347 return _error
->Errno("select","Select failed");
349 return _error
->Error("Could not connect, connection timed out");
350 unsigned int Err
,Len
=sizeof(Err
);
351 if (getsockopt(ServerFd
,SOL_SOCKET
,SO_ERROR
,&Err
,&Len
) != 0)
352 return _error
->Errno("getsockopt","Failed");
354 return _error
->Error("Could not connect.");
359 // ServerState::Close - Close a connection to the server /*{{{*/
360 // ---------------------------------------------------------------------
362 bool ServerState::Close()
369 // ServerState::RunHeaders - Get the headers before the data /*{{{*/
370 // ---------------------------------------------------------------------
371 /* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
372 parse error occured */
373 int ServerState::RunHeaders()
377 Owner
->Status("Waiting for file");
391 if (In
.WriteTillEl(Data
) == false)
394 for (string::const_iterator I
= Data
.begin(); I
< Data
.end(); I
++)
396 string::const_iterator J
= I
;
397 for (; J
!= Data
.end() && *J
!= '\n' && *J
!= '\r';J
++);
398 if (HeaderLine(string(I
,J
-I
)) == false)
404 while (Owner
->Go(false,this) == true);
409 // ServerState::RunData - Transfer the data from the socket /*{{{*/
410 // ---------------------------------------------------------------------
412 bool ServerState::RunData()
416 // Chunked transfer encoding is fun..
417 if (Encoding
== Chunked
)
421 // Grab the block size
427 if (In
.WriteTillEl(Data
,true) == true)
430 while ((Last
= Owner
->Go(false,this)) == true);
435 // See if we are done
436 unsigned long Len
= strtol(Data
.c_str(),0,16);
441 // We have to remove the entity trailer
445 if (In
.WriteTillEl(Data
,true) == true && Data
.length() <= 2)
448 while ((Last
= Owner
->Go(false,this)) == true);
451 return !_error
->PendingError();
454 // Transfer the block
456 while (Owner
->Go(true,this) == true)
457 if (In
.IsLimit() == true)
461 if (In
.IsLimit() == false)
464 // The server sends an extra new line before the next block specifier..
469 if (In
.WriteTillEl(Data
,true) == true)
472 while ((Last
= Owner
->Go(false,this)) == true);
479 /* Closes encoding is used when the server did not specify a size, the
480 loss of the connection means we are done */
481 if (Encoding
== Closes
)
484 In
.Limit(Size
- StartPos
);
486 // Just transfer the whole block.
489 if (In
.IsLimit() == false)
493 return !_error
->PendingError();
495 while (Owner
->Go(true,this) == true);
498 return Owner
->Flush(this) && !_error
->PendingError();
501 // ServerState::HeaderLine - Process a header line /*{{{*/
502 // ---------------------------------------------------------------------
504 bool ServerState::HeaderLine(string Line
)
506 if (Line
.empty() == true)
509 // The http server might be trying to do something evil.
510 if (Line
.length() >= MAXLEN
)
511 return _error
->Error("Got a single header line over %u chars",MAXLEN
);
513 string::size_type Pos
= Line
.find(' ');
514 if (Pos
== string::npos
|| Pos
+1 > Line
.length())
515 return _error
->Error("Bad header line");
517 string Tag
= string(Line
,0,Pos
);
518 string Val
= string(Line
,Pos
+1);
520 if (stringcasecmp(Tag
.begin(),Tag
.begin()+4,"HTTP") == 0)
522 // Evil servers return no version
525 if (sscanf(Line
.c_str(),"HTTP/%u.%u %u %[^\n]",&Major
,&Minor
,
527 return _error
->Error("The http server sent an invalid reply header");
533 if (sscanf(Line
.c_str(),"HTTP %u %[^\n]",&Result
,Code
) != 2)
534 return _error
->Error("The http server sent an invalid reply header");
540 if (stringcasecmp(Tag
,"Content-Length:") == 0)
542 if (Encoding
== Closes
)
546 // The length is already set from the Content-Range header
550 if (sscanf(Val
.c_str(),"%lu",&Size
) != 1)
551 return _error
->Error("The http server sent an invalid Content-Length header");
555 if (stringcasecmp(Tag
,"Content-Type:") == 0)
561 if (stringcasecmp(Tag
,"Content-Range:") == 0)
565 if (sscanf(Val
.c_str(),"bytes %lu-%*u/%lu",&StartPos
,&Size
) != 2)
566 return _error
->Error("The http server sent an invalid Content-Range header");
567 if ((unsigned)StartPos
> Size
)
568 return _error
->Error("This http server has broken range support");
572 if (stringcasecmp(Tag
,"Transfer-Encoding:") == 0)
575 if (stringcasecmp(Val
,"chunked") == 0)
581 if (stringcasecmp(Tag
,"Last-Modified:") == 0)
583 if (StrToTime(Val
,Date
) == false)
584 return _error
->Error("Unknown date format");
592 // HttpMethod::SendReq - Send the HTTP request /*{{{*/
593 // ---------------------------------------------------------------------
594 /* This places the http request in the outbound buffer */
595 void HttpMethod::SendReq(FetchItem
*Itm
,CircleBuf
&Out
)
599 // The HTTP server expects a hostname with a trailing :port
601 string ProperHost
= Uri
.Host
;
604 sprintf(Buf
,":%u",Uri
.Port
);
609 if (Itm
->Uri
.length() >= sizeof(Buf
))
612 /* Build the request. We include a keep-alive header only for non-proxy
613 requests. This is to tweak old http/1.0 servers that do support keep-alive
614 but not HTTP/1.1 automatic keep-alive. Doing this with a proxy server
615 will glitch HTTP/1.0 proxies because they do not filter it out and
616 pass it on, HTTP/1.1 says the connection should default to keep alive
617 and we expect the proxy to do this */
618 if (Proxy
.empty() == true)
619 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
620 QuoteString(Uri
.Path
,"~").c_str(),ProperHost
.c_str());
623 /* Generate a cache control header if necessary. We place a max
624 cache age on index files, optionally set a no-cache directive
625 and a no-store directive for archives. */
626 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\n",
627 Itm
->Uri
.c_str(),ProperHost
.c_str());
628 if (_config
->FindB("Acquire::http::No-Cache",false) == true)
629 strcat(Buf
,"Cache-Control: no-cache\r\nPragma: no-cache\r\n");
632 if (Itm
->IndexFile
== true)
633 sprintf(Buf
+strlen(Buf
),"Cache-Control: max-age=%u\r\n",
634 _config
->FindI("Acquire::http::Max-Age",60*60*24));
637 if (_config
->FindB("Acquire::http::No-Store",false) == true)
638 strcat(Buf
,"Cache-Control: no-store\r\n");
645 // Check for a partial file
647 if (stat(Itm
->DestFile
.c_str(),&SBuf
) >= 0 && SBuf
.st_size
> 0)
649 // In this case we send an if-range query with a range header
650 sprintf(Buf
,"Range: bytes=%li-\r\nIf-Range: %s\r\n",SBuf
.st_size
- 1,
651 TimeRFC1123(SBuf
.st_mtime
).c_str());
656 if (Itm
->LastModified
!= 0)
658 sprintf(Buf
,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm
->LastModified
).c_str());
663 if (Proxy
.User
.empty() == false || Proxy
.Password
.empty() == false)
664 Req
+= string("Proxy-Authorization: Basic ") +
665 Base64Encode(Proxy
.User
+ ":" + Proxy
.Password
) + "\r\n";
667 Req
+= "User-Agent: Debian APT-HTTP/1.2\r\n\r\n";
668 // cerr << Req << endl;
673 // HttpMethod::Go - Run a single loop /*{{{*/
674 // ---------------------------------------------------------------------
675 /* This runs the select loop over the server FDs, Output file FDs and
677 bool HttpMethod::Go(bool ToFile
,ServerState
*Srv
)
679 // Server has closed the connection
680 if (Srv
->ServerFd
== -1 && Srv
->In
.WriteSpace() == false)
683 fd_set rfds
,wfds
,efds
;
689 if (Srv
->Out
.WriteSpace() == true && Srv
->ServerFd
!= -1)
690 FD_SET(Srv
->ServerFd
,&wfds
);
691 if (Srv
->In
.ReadSpace() == true && Srv
->ServerFd
!= -1)
692 FD_SET(Srv
->ServerFd
,&rfds
);
699 if (Srv
->In
.WriteSpace() == true && ToFile
== true && FileFD
!= -1)
700 FD_SET(FileFD
,&wfds
);
703 FD_SET(STDIN_FILENO
,&rfds
);
707 FD_SET(FileFD
,&efds
);
708 if (Srv
->ServerFd
!= -1)
709 FD_SET(Srv
->ServerFd
,&efds
);
711 // Figure out the max fd
713 if (MaxFd
< Srv
->ServerFd
)
714 MaxFd
= Srv
->ServerFd
;
721 if ((Res
= select(MaxFd
+1,&rfds
,&wfds
,&efds
,&tv
)) < 0)
722 return _error
->Errno("select","Select failed");
726 _error
->Error("Connection timed out");
727 return ServerDie(Srv
);
730 // Some kind of exception (error) on the sockets, die
731 if ((FileFD
!= -1 && FD_ISSET(FileFD
,&efds
)) ||
732 (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&efds
)))
733 return _error
->Error("Socket Exception");
736 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&rfds
))
739 if (Srv
->In
.Read(Srv
->ServerFd
) == false)
740 return ServerDie(Srv
);
743 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&wfds
))
746 if (Srv
->Out
.Write(Srv
->ServerFd
) == false)
747 return ServerDie(Srv
);
750 // Send data to the file
751 if (FileFD
!= -1 && FD_ISSET(FileFD
,&wfds
))
753 if (Srv
->In
.Write(FileFD
) == false)
754 return _error
->Errno("write","Error writing to output file");
757 // Handle commands from APT
758 if (FD_ISSET(STDIN_FILENO
,&rfds
))
767 // HttpMethod::Flush - Dump the buffer into the file /*{{{*/
768 // ---------------------------------------------------------------------
769 /* This takes the current input buffer from the Server FD and writes it
771 bool HttpMethod::Flush(ServerState
*Srv
)
775 SetNonBlock(File
->Fd(),false);
776 if (Srv
->In
.WriteSpace() == false)
779 while (Srv
->In
.WriteSpace() == true)
781 if (Srv
->In
.Write(File
->Fd()) == false)
782 return _error
->Errno("write","Error writing to file");
783 if (Srv
->In
.IsLimit() == true)
787 if (Srv
->In
.IsLimit() == true || Srv
->Encoding
== ServerState::Closes
)
793 // HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
794 // ---------------------------------------------------------------------
796 bool HttpMethod::ServerDie(ServerState
*Srv
)
798 // Dump the buffer to the file
799 if (Srv
->State
== ServerState::Data
)
801 SetNonBlock(File
->Fd(),false);
802 while (Srv
->In
.WriteSpace() == true)
804 if (Srv
->In
.Write(File
->Fd()) == false)
805 return _error
->Errno("write","Error writing to the file");
808 if (Srv
->In
.IsLimit() == true)
813 // See if this is because the server finished the data stream
814 if (Srv
->In
.IsLimit() == false && Srv
->State
!= ServerState::Header
&&
815 Srv
->Encoding
!= ServerState::Closes
)
819 return _error
->Error("Error reading from server Remote end closed connection");
820 return _error
->Errno("read","Error reading from server");
826 // Nothing left in the buffer
827 if (Srv
->In
.WriteSpace() == false)
830 // We may have got multiple responses back in one packet..
838 // HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
839 // ---------------------------------------------------------------------
840 /* We look at the header data we got back from the server and decide what
844 3 - Unrecoverable error
845 4 - Error with error content page
846 5 - Unrecoverable non-server error (close the connection) */
847 int HttpMethod::DealWithHeaders(FetchResult
&Res
,ServerState
*Srv
)
850 if (Srv
->Result
== 304)
852 unlink(Queue
->DestFile
.c_str());
854 Res
.LastModified
= Queue
->LastModified
;
858 /* We have a reply we dont handle. This should indicate a perm server
860 if (Srv
->Result
< 200 || Srv
->Result
>= 300)
862 _error
->Error("%u %s",Srv
->Result
,Srv
->Code
);
863 if (Srv
->HaveContent
== true)
868 // This is some sort of 2xx 'data follows' reply
869 Res
.LastModified
= Srv
->Date
;
870 Res
.Size
= Srv
->Size
;
874 File
= new FileFd(Queue
->DestFile
,FileFd::WriteAny
);
875 if (_error
->PendingError() == true)
878 FailFile
= Queue
->DestFile
;
879 FailFile
.c_str(); // Make sure we don't do a malloc in the signal handler
881 FailTime
= Srv
->Date
;
883 // Set the expected size
884 if (Srv
->StartPos
>= 0)
886 Res
.ResumePoint
= Srv
->StartPos
;
887 ftruncate(File
->Fd(),Srv
->StartPos
);
890 // Set the start point
891 lseek(File
->Fd(),0,SEEK_END
);
894 Srv
->In
.MD5
= new MD5Summation
;
896 // Fill the MD5 Hash if the file is non-empty (resume)
897 if (Srv
->StartPos
> 0)
899 lseek(File
->Fd(),0,SEEK_SET
);
900 if (Srv
->In
.MD5
->AddFD(File
->Fd(),Srv
->StartPos
) == false)
902 _error
->Errno("read","Problem hashing file");
905 lseek(File
->Fd(),0,SEEK_END
);
908 SetNonBlock(File
->Fd(),true);
912 // HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
913 // ---------------------------------------------------------------------
914 /* This closes and timestamps the open file. This is neccessary to get
915 resume behavoir on user abort */
916 void HttpMethod::SigTerm(int)
925 UBuf
.actime
= FailTime
;
926 UBuf
.modtime
= FailTime
;
927 utime(FailFile
.c_str(),&UBuf
);
932 // HttpMethod::Fetch - Fetch an item /*{{{*/
933 // ---------------------------------------------------------------------
934 /* This adds an item to the pipeline. We keep the pipeline at a fixed
936 bool HttpMethod::Fetch(FetchItem
*)
941 // Queue the requests
944 for (FetchItem
*I
= Queue
; I
!= 0 && Depth
< (signed)PipelineDepth
; I
= I
->Next
, Depth
++)
946 // Make sure we stick with the same server
947 if (Server
->Comp(I
->Uri
) == false)
954 SendReq(I
,Server
->Out
);
962 // HttpMethod::Configuration - Handle a configuration message /*{{{*/
963 // ---------------------------------------------------------------------
964 /* We stash the desired pipeline depth */
965 bool HttpMethod::Configuration(string Message
)
967 if (pkgAcqMethod::Configuration(Message
) == false)
970 TimeOut
= _config
->FindI("Acquire::http::Timeout",TimeOut
);
971 PipelineDepth
= _config
->FindI("Acquire::http::Pipeline-Depth",
977 // HttpMethod::Loop - Main loop /*{{{*/
978 // ---------------------------------------------------------------------
980 int HttpMethod::Loop()
982 signal(SIGTERM
,SigTerm
);
983 signal(SIGINT
,SigTerm
);
990 if (FailCounter
>= 2)
992 Fail("Massive Server Brain Damage",true);
996 // We have no commands, wait for some to arrive
999 if (WaitFd(STDIN_FILENO
) == false)
1010 // Connect to the server
1011 if (Server
== 0 || Server
->Comp(Queue
->Uri
) == false)
1014 Server
= new ServerState(Queue
->Uri
,this);
1017 // Reset the pipeline
1018 if (Server
->ServerFd
== -1)
1021 // Connnect to the host
1022 if (Server
->Open() == false)
1030 // Fill the pipeline.
1033 // Fetch the next URL header data from the server.
1034 switch (Server
->RunHeaders())
1039 // The header data is bad
1042 _error
->Error("Bad header Data");
1047 // The server closed a connection during the header get..
1058 // Decide what to do.
1060 Res
.Filename
= Queue
->DestFile
;
1061 switch (DealWithHeaders(Res
,Server
))
1063 // Ok, the file is Open
1069 bool Result
= Server
->RunData();
1071 // Close the file, destroy the FD object and timestamp it
1077 struct utimbuf UBuf
;
1079 UBuf
.actime
= Server
->Date
;
1080 UBuf
.modtime
= Server
->Date
;
1081 utime(Queue
->DestFile
.c_str(),&UBuf
);
1083 // Send status to APT
1086 Res
.MD5Sum
= Server
->In
.MD5
->Result();
1102 // Hard server error, not found or something
1109 // Hard internal error, kill the connection and fail
1117 // We need to flush the data, the header is like a 404 w/ error text
1122 // Send to content to dev/null
1123 File
= new FileFd("/dev/null",FileFd::WriteExists
);
1131 Fail("Internal error");