]>
git.saurik.com Git - apt.git/blob - methods/http.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: http.cc,v 1.38 1999/10/03 21:09:27 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>
47 #include "rfc2553emu.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;
59 // CircleBuf::CircleBuf - Circular input buffer /*{{{*/
60 // ---------------------------------------------------------------------
62 CircleBuf::CircleBuf(unsigned long Size
) : Size(Size
), MD5(0)
64 Buf
= new unsigned char[Size
];
68 // CircleBuf::Reset - Reset to the default state /*{{{*/
69 // ---------------------------------------------------------------------
71 void CircleBuf::Reset()
76 MaxGet
= (unsigned int)-1;
81 MD5
= new MD5Summation
;
85 // CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/
86 // ---------------------------------------------------------------------
87 /* This fills up the buffer with as much data as is in the FD, assuming it
89 bool CircleBuf::Read(int Fd
)
93 // Woops, buffer is full
94 if (InP
- OutP
== Size
)
97 // Write the buffer segment
99 Res
= read(Fd
,Buf
+ (InP%Size
),LeftRead());
111 gettimeofday(&Start
,0);
116 // CircleBuf::Read - Put the string into the buffer /*{{{*/
117 // ---------------------------------------------------------------------
118 /* This will hold the string in and fill the buffer with it as it empties */
119 bool CircleBuf::Read(string Data
)
126 // CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/
127 // ---------------------------------------------------------------------
129 void CircleBuf::FillOut()
131 if (OutQueue
.empty() == true)
135 // Woops, buffer is full
136 if (InP
- OutP
== Size
)
139 // Write the buffer segment
140 unsigned long Sz
= LeftRead();
141 if (OutQueue
.length() - StrPos
< Sz
)
142 Sz
= OutQueue
.length() - StrPos
;
143 memcpy(Buf
+ (InP%Size
),OutQueue
.begin() + StrPos
,Sz
);
148 if (OutQueue
.length() == StrPos
)
157 // CircleBuf::Write - Write from the buffer into a FD /*{{{*/
158 // ---------------------------------------------------------------------
159 /* This empties the buffer into the FD. */
160 bool CircleBuf::Write(int Fd
)
166 // Woops, buffer is empty
173 // Write the buffer segment
175 Res
= write(Fd
,Buf
+ (OutP%Size
),LeftWrite());
188 MD5
->Add(Buf
+ (OutP%Size
),Res
);
194 // CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/
195 // ---------------------------------------------------------------------
196 /* This copies till the first empty line */
197 bool CircleBuf::WriteTillEl(string
&Data
,bool Single
)
199 // We cheat and assume it is unneeded to have more than one buffer load
200 for (unsigned long I
= OutP
; I
< InP
; I
++)
202 if (Buf
[I%Size
] != '\n')
204 for (I
++; I
< InP
&& Buf
[I%Size
] == '\r'; I
++);
208 if (Buf
[I%Size
] != '\n')
210 for (I
++; I
< InP
&& Buf
[I%Size
] == '\r'; I
++);
219 unsigned long Sz
= LeftWrite();
222 if (I
- OutP
< LeftWrite())
224 Data
+= string((char *)(Buf
+ (OutP%Size
)),Sz
);
232 // CircleBuf::Stats - Print out stats information /*{{{*/
233 // ---------------------------------------------------------------------
235 void CircleBuf::Stats()
241 gettimeofday(&Stop
,0);
242 /* float Diff = Stop.tv_sec - Start.tv_sec +
243 (float)(Stop.tv_usec - Start.tv_usec)/1000000;
244 clog << "Got " << InP << " in " << Diff << " at " << InP/Diff << endl;*/
248 // ServerState::ServerState - Constructor /*{{{*/
249 // ---------------------------------------------------------------------
251 ServerState::ServerState(URI Srv
,HttpMethod
*Owner
) : Owner(Owner
),
252 In(64*1024), Out(4*1024),
258 // ServerState::Open - Open a connection to the server /*{{{*/
259 // ---------------------------------------------------------------------
260 /* This opens a connection to the server. */
263 struct addrinfo
*LastHostAddr
= 0;
264 bool ServerState::Open()
266 // Use the already open connection if possible.
274 // Determine the proxy setting
275 if (getenv("http_proxy") == 0)
277 string DefProxy
= _config
->Find("Acquire::http::Proxy");
278 string SpecificProxy
= _config
->Find("Acquire::http::Proxy::" + ServerName
.Host
);
279 if (SpecificProxy
.empty() == false)
281 if (SpecificProxy
== "DIRECT")
284 Proxy
= SpecificProxy
;
290 Proxy
= getenv("http_proxy");
292 // Determine what host and port to use based on the proxy settings
295 if (Proxy
.empty() == true)
297 if (ServerName
.Port
!= 0)
298 Port
= ServerName
.Port
;
299 Host
= ServerName
.Host
;
308 // Connect to the remote server
309 if (Connect(Host
,Port
,"http",80,ServerFd
,TimeOut
,Owner
) == false)
315 // ServerState::Close - Close a connection to the server /*{{{*/
316 // ---------------------------------------------------------------------
318 bool ServerState::Close()
325 // ServerState::RunHeaders - Get the headers before the data /*{{{*/
326 // ---------------------------------------------------------------------
327 /* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
328 parse error occured */
329 int ServerState::RunHeaders()
333 Owner
->Status("Waiting for file");
347 if (In
.WriteTillEl(Data
) == false)
350 for (string::const_iterator I
= Data
.begin(); I
< Data
.end(); I
++)
352 string::const_iterator J
= I
;
353 for (; J
!= Data
.end() && *J
!= '\n' && *J
!= '\r';J
++);
354 if (HeaderLine(string(I
,J
-I
)) == false)
360 while (Owner
->Go(false,this) == true);
365 // ServerState::RunData - Transfer the data from the socket /*{{{*/
366 // ---------------------------------------------------------------------
368 bool ServerState::RunData()
372 // Chunked transfer encoding is fun..
373 if (Encoding
== Chunked
)
377 // Grab the block size
383 if (In
.WriteTillEl(Data
,true) == true)
386 while ((Last
= Owner
->Go(false,this)) == true);
391 // See if we are done
392 unsigned long Len
= strtol(Data
.c_str(),0,16);
397 // We have to remove the entity trailer
401 if (In
.WriteTillEl(Data
,true) == true && Data
.length() <= 2)
404 while ((Last
= Owner
->Go(false,this)) == true);
407 return !_error
->PendingError();
410 // Transfer the block
412 while (Owner
->Go(true,this) == true)
413 if (In
.IsLimit() == true)
417 if (In
.IsLimit() == false)
420 // The server sends an extra new line before the next block specifier..
425 if (In
.WriteTillEl(Data
,true) == true)
428 while ((Last
= Owner
->Go(false,this)) == true);
435 /* Closes encoding is used when the server did not specify a size, the
436 loss of the connection means we are done */
437 if (Encoding
== Closes
)
440 In
.Limit(Size
- StartPos
);
442 // Just transfer the whole block.
445 if (In
.IsLimit() == false)
449 return !_error
->PendingError();
451 while (Owner
->Go(true,this) == true);
454 return Owner
->Flush(this) && !_error
->PendingError();
457 // ServerState::HeaderLine - Process a header line /*{{{*/
458 // ---------------------------------------------------------------------
460 bool ServerState::HeaderLine(string Line
)
462 if (Line
.empty() == true)
465 // The http server might be trying to do something evil.
466 if (Line
.length() >= MAXLEN
)
467 return _error
->Error("Got a single header line over %u chars",MAXLEN
);
469 string::size_type Pos
= Line
.find(' ');
470 if (Pos
== string::npos
|| Pos
+1 > Line
.length())
472 // Blah, some servers use "connection:closes", evil.
473 Pos
= Line
.find(':');
474 if (Pos
== string::npos
|| Pos
+ 2 > Line
.length())
475 return _error
->Error("Bad header line");
479 // Parse off any trailing spaces between the : and the next word.
480 string::size_type Pos2
= Pos
;
481 while (Pos2
< Line
.length() && isspace(Line
[Pos2
]) != 0)
484 string Tag
= string(Line
,0,Pos
);
485 string Val
= string(Line
,Pos2
);
487 if (stringcasecmp(Tag
.begin(),Tag
.begin()+4,"HTTP") == 0)
489 // Evil servers return no version
492 if (sscanf(Line
.c_str(),"HTTP/%u.%u %u %[^\n]",&Major
,&Minor
,
494 return _error
->Error("The http server sent an invalid reply header");
500 if (sscanf(Line
.c_str(),"HTTP %u %[^\n]",&Result
,Code
) != 2)
501 return _error
->Error("The http server sent an invalid reply header");
507 if (stringcasecmp(Tag
,"Content-Length:") == 0)
509 if (Encoding
== Closes
)
513 // The length is already set from the Content-Range header
517 if (sscanf(Val
.c_str(),"%lu",&Size
) != 1)
518 return _error
->Error("The http server sent an invalid Content-Length header");
522 if (stringcasecmp(Tag
,"Content-Type:") == 0)
528 if (stringcasecmp(Tag
,"Content-Range:") == 0)
532 if (sscanf(Val
.c_str(),"bytes %lu-%*u/%lu",&StartPos
,&Size
) != 2)
533 return _error
->Error("The http server sent an invalid Content-Range header");
534 if ((unsigned)StartPos
> Size
)
535 return _error
->Error("This http server has broken range support");
539 if (stringcasecmp(Tag
,"Transfer-Encoding:") == 0)
542 if (stringcasecmp(Val
,"chunked") == 0)
548 if (stringcasecmp(Tag
,"Last-Modified:") == 0)
550 if (StrToTime(Val
,Date
) == false)
551 return _error
->Error("Unknown date format");
559 // HttpMethod::SendReq - Send the HTTP request /*{{{*/
560 // ---------------------------------------------------------------------
561 /* This places the http request in the outbound buffer */
562 void HttpMethod::SendReq(FetchItem
*Itm
,CircleBuf
&Out
)
566 // The HTTP server expects a hostname with a trailing :port
568 string ProperHost
= Uri
.Host
;
571 sprintf(Buf
,":%u",Uri
.Port
);
576 if (Itm
->Uri
.length() >= sizeof(Buf
))
579 /* Build the request. We include a keep-alive header only for non-proxy
580 requests. This is to tweak old http/1.0 servers that do support keep-alive
581 but not HTTP/1.1 automatic keep-alive. Doing this with a proxy server
582 will glitch HTTP/1.0 proxies because they do not filter it out and
583 pass it on, HTTP/1.1 says the connection should default to keep alive
584 and we expect the proxy to do this */
585 if (Proxy
.empty() == true)
586 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
587 QuoteString(Uri
.Path
,"~").c_str(),ProperHost
.c_str());
590 /* Generate a cache control header if necessary. We place a max
591 cache age on index files, optionally set a no-cache directive
592 and a no-store directive for archives. */
593 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\n",
594 Itm
->Uri
.c_str(),ProperHost
.c_str());
595 if (_config
->FindB("Acquire::http::No-Cache",false) == true)
596 strcat(Buf
,"Cache-Control: no-cache\r\nPragma: no-cache\r\n");
599 if (Itm
->IndexFile
== true)
600 sprintf(Buf
+strlen(Buf
),"Cache-Control: max-age=%u\r\n",
601 _config
->FindI("Acquire::http::Max-Age",60*60*24));
604 if (_config
->FindB("Acquire::http::No-Store",false) == true)
605 strcat(Buf
,"Cache-Control: no-store\r\n");
612 // Check for a partial file
614 if (stat(Itm
->DestFile
.c_str(),&SBuf
) >= 0 && SBuf
.st_size
> 0)
616 // In this case we send an if-range query with a range header
617 sprintf(Buf
,"Range: bytes=%li-\r\nIf-Range: %s\r\n",SBuf
.st_size
- 1,
618 TimeRFC1123(SBuf
.st_mtime
).c_str());
623 if (Itm
->LastModified
!= 0)
625 sprintf(Buf
,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm
->LastModified
).c_str());
630 if (Proxy
.User
.empty() == false || Proxy
.Password
.empty() == false)
631 Req
+= string("Proxy-Authorization: Basic ") +
632 Base64Encode(Proxy
.User
+ ":" + Proxy
.Password
) + "\r\n";
634 Req
+= "User-Agent: Debian APT-HTTP/1.2\r\n\r\n";
642 // HttpMethod::Go - Run a single loop /*{{{*/
643 // ---------------------------------------------------------------------
644 /* This runs the select loop over the server FDs, Output file FDs and
646 bool HttpMethod::Go(bool ToFile
,ServerState
*Srv
)
648 // Server has closed the connection
649 if (Srv
->ServerFd
== -1 && (Srv
->In
.WriteSpace() == false ||
653 fd_set rfds
,wfds
,efds
;
659 if (Srv
->Out
.WriteSpace() == true && Srv
->ServerFd
!= -1)
660 FD_SET(Srv
->ServerFd
,&wfds
);
661 if (Srv
->In
.ReadSpace() == true && Srv
->ServerFd
!= -1)
662 FD_SET(Srv
->ServerFd
,&rfds
);
669 if (Srv
->In
.WriteSpace() == true && ToFile
== true && FileFD
!= -1)
670 FD_SET(FileFD
,&wfds
);
673 FD_SET(STDIN_FILENO
,&rfds
);
677 FD_SET(FileFD
,&efds
);
678 if (Srv
->ServerFd
!= -1)
679 FD_SET(Srv
->ServerFd
,&efds
);
681 // Figure out the max fd
683 if (MaxFd
< Srv
->ServerFd
)
684 MaxFd
= Srv
->ServerFd
;
691 if ((Res
= select(MaxFd
+1,&rfds
,&wfds
,&efds
,&tv
)) < 0)
692 return _error
->Errno("select","Select failed");
696 _error
->Error("Connection timed out");
697 return ServerDie(Srv
);
700 // Some kind of exception (error) on the sockets, die
701 if ((FileFD
!= -1 && FD_ISSET(FileFD
,&efds
)) ||
702 (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&efds
)))
703 return _error
->Error("Socket Exception");
706 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&rfds
))
709 if (Srv
->In
.Read(Srv
->ServerFd
) == false)
710 return ServerDie(Srv
);
713 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&wfds
))
716 if (Srv
->Out
.Write(Srv
->ServerFd
) == false)
717 return ServerDie(Srv
);
720 // Send data to the file
721 if (FileFD
!= -1 && FD_ISSET(FileFD
,&wfds
))
723 if (Srv
->In
.Write(FileFD
) == false)
724 return _error
->Errno("write","Error writing to output file");
727 // Handle commands from APT
728 if (FD_ISSET(STDIN_FILENO
,&rfds
))
737 // HttpMethod::Flush - Dump the buffer into the file /*{{{*/
738 // ---------------------------------------------------------------------
739 /* This takes the current input buffer from the Server FD and writes it
741 bool HttpMethod::Flush(ServerState
*Srv
)
745 SetNonBlock(File
->Fd(),false);
746 if (Srv
->In
.WriteSpace() == false)
749 while (Srv
->In
.WriteSpace() == true)
751 if (Srv
->In
.Write(File
->Fd()) == false)
752 return _error
->Errno("write","Error writing to file");
753 if (Srv
->In
.IsLimit() == true)
757 if (Srv
->In
.IsLimit() == true || Srv
->Encoding
== ServerState::Closes
)
763 // HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
764 // ---------------------------------------------------------------------
766 bool HttpMethod::ServerDie(ServerState
*Srv
)
768 unsigned int LErrno
= errno
;
770 // Dump the buffer to the file
771 if (Srv
->State
== ServerState::Data
)
773 SetNonBlock(File
->Fd(),false);
774 while (Srv
->In
.WriteSpace() == true)
776 if (Srv
->In
.Write(File
->Fd()) == false)
777 return _error
->Errno("write","Error writing to the file");
780 if (Srv
->In
.IsLimit() == true)
785 // See if this is because the server finished the data stream
786 if (Srv
->In
.IsLimit() == false && Srv
->State
!= ServerState::Header
&&
787 Srv
->Encoding
!= ServerState::Closes
)
791 return _error
->Error("Error reading from server Remote end closed connection");
793 return _error
->Errno("read","Error reading from server");
799 // Nothing left in the buffer
800 if (Srv
->In
.WriteSpace() == false)
803 // We may have got multiple responses back in one packet..
811 // HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
812 // ---------------------------------------------------------------------
813 /* We look at the header data we got back from the server and decide what
817 3 - Unrecoverable error
818 4 - Error with error content page
819 5 - Unrecoverable non-server error (close the connection) */
820 int HttpMethod::DealWithHeaders(FetchResult
&Res
,ServerState
*Srv
)
823 if (Srv
->Result
== 304)
825 unlink(Queue
->DestFile
.c_str());
827 Res
.LastModified
= Queue
->LastModified
;
831 /* We have a reply we dont handle. This should indicate a perm server
833 if (Srv
->Result
< 200 || Srv
->Result
>= 300)
835 _error
->Error("%u %s",Srv
->Result
,Srv
->Code
);
836 if (Srv
->HaveContent
== true)
841 // This is some sort of 2xx 'data follows' reply
842 Res
.LastModified
= Srv
->Date
;
843 Res
.Size
= Srv
->Size
;
847 File
= new FileFd(Queue
->DestFile
,FileFd::WriteAny
);
848 if (_error
->PendingError() == true)
851 FailFile
= Queue
->DestFile
;
852 FailFile
.c_str(); // Make sure we dont do a malloc in the signal handler
854 FailTime
= Srv
->Date
;
856 // Set the expected size
857 if (Srv
->StartPos
>= 0)
859 Res
.ResumePoint
= Srv
->StartPos
;
860 ftruncate(File
->Fd(),Srv
->StartPos
);
863 // Set the start point
864 lseek(File
->Fd(),0,SEEK_END
);
867 Srv
->In
.MD5
= new MD5Summation
;
869 // Fill the MD5 Hash if the file is non-empty (resume)
870 if (Srv
->StartPos
> 0)
872 lseek(File
->Fd(),0,SEEK_SET
);
873 if (Srv
->In
.MD5
->AddFD(File
->Fd(),Srv
->StartPos
) == false)
875 _error
->Errno("read","Problem hashing file");
878 lseek(File
->Fd(),0,SEEK_END
);
881 SetNonBlock(File
->Fd(),true);
885 // HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
886 // ---------------------------------------------------------------------
887 /* This closes and timestamps the open file. This is neccessary to get
888 resume behavoir on user abort */
889 void HttpMethod::SigTerm(int)
897 UBuf
.actime
= FailTime
;
898 UBuf
.modtime
= FailTime
;
899 utime(FailFile
.c_str(),&UBuf
);
904 // HttpMethod::Fetch - Fetch an item /*{{{*/
905 // ---------------------------------------------------------------------
906 /* This adds an item to the pipeline. We keep the pipeline at a fixed
908 bool HttpMethod::Fetch(FetchItem
*)
913 // Queue the requests
916 for (FetchItem
*I
= Queue
; I
!= 0 && Depth
< (signed)PipelineDepth
; I
= I
->Next
, Depth
++)
918 // Make sure we stick with the same server
919 if (Server
->Comp(I
->Uri
) == false)
926 SendReq(I
,Server
->Out
);
934 // HttpMethod::Configuration - Handle a configuration message /*{{{*/
935 // ---------------------------------------------------------------------
936 /* We stash the desired pipeline depth */
937 bool HttpMethod::Configuration(string Message
)
939 if (pkgAcqMethod::Configuration(Message
) == false)
942 TimeOut
= _config
->FindI("Acquire::http::Timeout",TimeOut
);
943 PipelineDepth
= _config
->FindI("Acquire::http::Pipeline-Depth",
945 Debug
= _config
->FindB("Debug::Acquire::http",false);
950 // HttpMethod::Loop - Main loop /*{{{*/
951 // ---------------------------------------------------------------------
953 int HttpMethod::Loop()
955 signal(SIGTERM
,SigTerm
);
956 signal(SIGINT
,SigTerm
);
963 // We have no commands, wait for some to arrive
966 if (WaitFd(STDIN_FILENO
) == false)
970 /* Run messages, we can accept 0 (no message) if we didn't
971 do a WaitFd above.. Otherwise the FD is closed. */
972 int Result
= Run(true);
973 if (Result
!= -1 && (Result
!= 0 || Queue
== 0))
979 // Connect to the server
980 if (Server
== 0 || Server
->Comp(Queue
->Uri
) == false)
983 Server
= new ServerState(Queue
->Uri
,this);
986 // Reset the pipeline
987 if (Server
->ServerFd
== -1)
990 // Connnect to the host
991 if (Server
->Open() == false)
999 // Fill the pipeline.
1002 // Fetch the next URL header data from the server.
1003 switch (Server
->RunHeaders())
1008 // The header data is bad
1011 _error
->Error("Bad header Data");
1016 // The server closed a connection during the header get..
1024 if (FailCounter
>= 2)
1026 Fail("Connection failed",true);
1034 // Decide what to do.
1036 Res
.Filename
= Queue
->DestFile
;
1037 switch (DealWithHeaders(Res
,Server
))
1039 // Ok, the file is Open
1045 bool Result
= Server
->RunData();
1047 // Close the file, destroy the FD object and timestamp it
1053 struct utimbuf UBuf
;
1055 UBuf
.actime
= Server
->Date
;
1056 UBuf
.modtime
= Server
->Date
;
1057 utime(Queue
->DestFile
.c_str(),&UBuf
);
1059 // Send status to APT
1062 Res
.MD5Sum
= Server
->In
.MD5
->Result();
1078 // Hard server error, not found or something
1085 // Hard internal error, kill the connection and fail
1093 // We need to flush the data, the header is like a 404 w/ error text
1098 // Send to content to dev/null
1099 File
= new FileFd("/dev/null",FileFd::WriteExists
);
1107 Fail("Internal error");