]>
git.saurik.com Git - apt.git/blob - methods/http.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: http.cc,v 1.7 1998/11/23 21:28:43 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>
43 #include <netinet/in.h>
44 #include <sys/socket.h>
45 #include <arpa/inet.h>
51 string
HttpMethod::FailFile
;
52 int HttpMethod::FailFd
= -1;
53 time_t HttpMethod::FailTime
= 0;
55 // CircleBuf::CircleBuf - Circular input buffer /*{{{*/
56 // ---------------------------------------------------------------------
58 CircleBuf::CircleBuf(unsigned long Size
) : Size(Size
), MD5(0)
60 Buf
= new unsigned char[Size
];
64 // CircleBuf::Reset - Reset to the default state /*{{{*/
65 // ---------------------------------------------------------------------
67 void CircleBuf::Reset()
72 MaxGet
= (unsigned int)-1;
77 MD5
= new MD5Summation
;
81 // CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/
82 // ---------------------------------------------------------------------
83 /* This fills up the buffer with as much data as is in the FD, assuming it
85 bool CircleBuf::Read(int Fd
)
89 // Woops, buffer is full
90 if (InP
- OutP
== Size
)
93 // Write the buffer segment
95 Res
= read(Fd
,Buf
+ (InP%Size
),LeftRead());
107 gettimeofday(&Start
,0);
112 // CircleBuf::Read - Put the string into the buffer /*{{{*/
113 // ---------------------------------------------------------------------
114 /* This will hold the string in and fill the buffer with it as it empties */
115 bool CircleBuf::Read(string Data
)
122 // CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/
123 // ---------------------------------------------------------------------
125 void CircleBuf::FillOut()
127 if (OutQueue
.empty() == true)
131 // Woops, buffer is full
132 if (InP
- OutP
== Size
)
135 // Write the buffer segment
136 unsigned long Sz
= LeftRead();
137 if (OutQueue
.length() - StrPos
< Sz
)
138 Sz
= OutQueue
.length() - StrPos
;
139 memcpy(Buf
+ (InP%Size
),OutQueue
.begin() + StrPos
,Sz
);
144 if (OutQueue
.length() == StrPos
)
153 // CircleBuf::Write - Write from the buffer into a FD /*{{{*/
154 // ---------------------------------------------------------------------
155 /* This empties the buffer into the FD. */
156 bool CircleBuf::Write(int Fd
)
162 // Woops, buffer is empty
169 // Write the buffer segment
171 Res
= write(Fd
,Buf
+ (OutP%Size
),LeftWrite());
184 MD5
->Add(Buf
+ (OutP%Size
),Res
);
190 // CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/
191 // ---------------------------------------------------------------------
192 /* This copies till the first empty line */
193 bool CircleBuf::WriteTillEl(string
&Data
,bool Single
)
195 // We cheat and assume it is unneeded to have more than one buffer load
196 for (unsigned long I
= OutP
; I
< InP
; I
++)
198 if (Buf
[I%Size
] != '\n')
200 for (I
++; I
< InP
&& Buf
[I%Size
] == '\r'; I
++);
204 if (Buf
[I%Size
] != '\n')
206 for (I
++; I
< InP
&& Buf
[I%Size
] == '\r'; I
++);
215 unsigned long Sz
= LeftWrite();
218 if (I
- OutP
< LeftWrite())
220 Data
+= string((char *)(Buf
+ (OutP%Size
)),Sz
);
228 // CircleBuf::Stats - Print out stats information /*{{{*/
229 // ---------------------------------------------------------------------
231 void CircleBuf::Stats()
237 gettimeofday(&Stop
,0);
238 /* float Diff = Stop.tv_sec - Start.tv_sec +
239 (float)(Stop.tv_usec - Start.tv_usec)/1000000;
240 clog << "Got " << InP << " in " << Diff << " at " << InP/Diff << endl;*/
244 // ServerState::ServerState - Constructor /*{{{*/
245 // ---------------------------------------------------------------------
247 ServerState::ServerState(URI Srv
,HttpMethod
*Owner
) : Owner(Owner
),
248 In(64*1024), Out(1*1024),
254 // ServerState::Open - Open a connection to the server /*{{{*/
255 // ---------------------------------------------------------------------
256 /* This opens a connection to the server. */
259 bool ServerState::Open()
261 // Use the already open connection if possible.
269 // Determine the proxy setting
270 if (getenv("http_proxy") != 0)
272 string DefProxy
= _config
->Find("Acquire::http::Proxy");
273 string SpecificProxy
= _config
->Find("Acquire::http::Proxy::" + ServerName
.Host
);
274 if (SpecificProxy
.empty() == false)
276 if (SpecificProxy
== "DIRECT")
279 Proxy
= SpecificProxy
;
285 Proxy
= getenv("http_proxy");
287 // Determine what host and port to use based on the proxy settings
290 if (Proxy
.empty() == true)
292 if (ServerName
.Port
!= 0)
293 Port
= ServerName
.Port
;
294 Host
= ServerName
.Host
;
303 /* We used a cached address record.. Yes this is against the spec but
304 the way we have setup our rotating dns suggests that this is more
306 if (LastHost
!= Host
)
308 Owner
->Status("Connecting to %s",Host
.c_str());
311 hostent
*Addr
= gethostbyname(Host
.c_str());
313 return _error
->Error("Could not resolve '%s'",Host
.c_str());
315 LastHostA
= *(in_addr
*)(Addr
->h_addr_list
[0]);
318 Owner
->Status("Connecting to %s (%s)",Host
.c_str(),inet_ntoa(LastHostA
));
321 if ((ServerFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
322 return _error
->Errno("socket","Could not create a socket");
324 // Connect to the server
325 struct sockaddr_in server
;
326 server
.sin_family
= AF_INET
;
327 server
.sin_port
= htons(Port
);
328 server
.sin_addr
= LastHostA
;
329 if (connect(ServerFd
,(sockaddr
*)&server
,sizeof(server
)) < 0)
330 return _error
->Errno("socket","Could not create a socket");
332 SetNonBlock(ServerFd
,true);
336 // ServerState::Close - Close a connection to the server /*{{{*/
337 // ---------------------------------------------------------------------
339 bool ServerState::Close()
346 // ServerState::RunHeaders - Get the headers before the data /*{{{*/
347 // ---------------------------------------------------------------------
348 /* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
349 parse error occured */
350 int ServerState::RunHeaders()
354 Owner
->Status("Waiting for file");
368 if (In
.WriteTillEl(Data
) == false)
371 for (string::const_iterator I
= Data
.begin(); I
< Data
.end(); I
++)
373 string::const_iterator J
= I
;
374 for (; J
!= Data
.end() && *J
!= '\n' && *J
!= '\r';J
++);
375 if (HeaderLine(string(I
,J
-I
)) == false)
381 while (Owner
->Go(false,this) == true);
386 // ServerState::RunData - Transfer the data from the socket /*{{{*/
387 // ---------------------------------------------------------------------
389 bool ServerState::RunData()
393 // Chunked transfer encoding is fun..
394 if (Encoding
== Chunked
)
398 // Grab the block size
404 if (In
.WriteTillEl(Data
,true) == true)
407 while ((Last
= Owner
->Go(false,this)) == true);
412 // See if we are done
413 unsigned long Len
= strtol(Data
.c_str(),0,16);
418 // We have to remove the entity trailer
422 if (In
.WriteTillEl(Data
,true) == true && Data
.length() <= 2)
425 while ((Last
= Owner
->Go(false,this)) == true);
431 // Transfer the block
433 while (Owner
->Go(true,this) == true)
434 if (In
.IsLimit() == true)
438 if (In
.IsLimit() == false)
441 // The server sends an extra new line before the next block specifier..
446 if (In
.WriteTillEl(Data
,true) == true)
449 while ((Last
= Owner
->Go(false,this)) == true);
456 /* Closes encoding is used when the server did not specify a size, the
457 loss of the connection means we are done */
458 if (Encoding
== Closes
)
461 In
.Limit(Size
- StartPos
);
463 // Just transfer the whole block.
466 if (In
.IsLimit() == false)
472 while (Owner
->Go(true,this) == true);
475 return Owner
->Flush(this);
478 // ServerState::HeaderLine - Process a header line /*{{{*/
479 // ---------------------------------------------------------------------
481 bool ServerState::HeaderLine(string Line
)
483 if (Line
.empty() == true)
486 // The http server might be trying to do something evil.
487 if (Line
.length() >= MAXLEN
)
488 return _error
->Error("Got a single header line over %u chars",MAXLEN
);
490 string::size_type Pos
= Line
.find(' ');
491 if (Pos
== string::npos
|| Pos
+1 > Line
.length())
492 return _error
->Error("Bad header line");
494 string Tag
= string(Line
,0,Pos
);
495 string Val
= string(Line
,Pos
+1);
497 if (stringcasecmp(Tag
.begin(),Tag
.begin()+4,"HTTP") == 0)
499 // Evil servers return no version
502 if (sscanf(Line
.c_str(),"HTTP/%u.%u %u %[^\n]",&Major
,&Minor
,
504 return _error
->Error("The http server sent an invalid reply header");
510 if (sscanf(Line
.c_str(),"HTTP %u %[^\n]",&Result
,Code
) != 2)
511 return _error
->Error("The http server sent an invalid reply header");
517 if (stringcasecmp(Tag
,"Content-Length:") == 0)
519 if (Encoding
== Closes
)
523 // The length is already set from the Content-Range header
527 if (sscanf(Val
.c_str(),"%lu",&Size
) != 1)
528 return _error
->Error("The http server sent an invalid Content-Length header");
532 if (stringcasecmp(Tag
,"Content-Type:") == 0)
538 if (stringcasecmp(Tag
,"Content-Range:") == 0)
542 if (sscanf(Val
.c_str(),"bytes %lu-%*u/%lu",&StartPos
,&Size
) != 2)
543 return _error
->Error("The http server sent an invalid Content-Range header");
544 if ((unsigned)StartPos
> Size
)
545 return _error
->Error("This http server has broken range support");
549 if (stringcasecmp(Tag
,"Transfer-Encoding:") == 0)
552 if (stringcasecmp(Val
,"chunked") == 0)
558 if (stringcasecmp(Tag
,"Last-Modified:") == 0)
560 if (StrToTime(Val
,Date
) == false)
561 return _error
->Error("Unknown date format");
569 // HttpMethod::SendReq - Send the HTTP request /*{{{*/
570 // ---------------------------------------------------------------------
571 /* This places the http request in the outbound buffer */
572 void HttpMethod::SendReq(FetchItem
*Itm
,CircleBuf
&Out
)
576 // The HTTP server expects a hostname with a trailing :port
578 string ProperHost
= Uri
.Host
;
581 sprintf(Buf
,":%u",Uri
.Port
);
585 /* Build the request. We include a keep-alive header only for non-proxy
586 requests. This is to tweak old http/1.0 servers that do support keep-alive
587 but not HTTP/1.1 automatic keep-alive. Doing this with a proxy server
588 will glitch HTTP/1.0 proxies because they do not filter it out and
589 pass it on, HTTP/1.1 says the connection should default to keep alive
590 and we expect the proxy to do this */
591 if (Proxy
.empty() == true)
592 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
593 Uri
.Path
.c_str(),ProperHost
.c_str());
595 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\n",
596 Itm
->Uri
.c_str(),ProperHost
.c_str());
599 // Check for a partial file
601 if (stat(Itm
->DestFile
.c_str(),&SBuf
) >= 0 && SBuf
.st_size
> 0)
603 // In this case we send an if-range query with a range header
604 sprintf(Buf
,"Range: bytes=%li-\r\nIf-Range: %s\r\n",SBuf
.st_size
- 1,
605 TimeRFC1123(SBuf
.st_mtime
).c_str());
610 if (Itm
->LastModified
!= 0)
612 sprintf(Buf
,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm
->LastModified
).c_str());
617 /* if (ProxyAuth.empty() == false)
618 Req += string("Proxy-Authorization: Basic ") + Base64Encode(ProxyAuth) + "\r\n";*/
620 Req
+= "User-Agent: Debian APT-HTTP/1.2\r\n\r\n";
621 // cerr << Req << endl;
626 // HttpMethod::Go - Run a single loop /*{{{*/
627 // ---------------------------------------------------------------------
628 /* This runs the select loop over the server FDs, Output file FDs and
630 bool HttpMethod::Go(bool ToFile
,ServerState
*Srv
)
632 // Server has closed the connection
633 if (Srv
->ServerFd
== -1 && Srv
->In
.WriteSpace() == false)
636 fd_set rfds
,wfds
,efds
;
642 if (Srv
->Out
.WriteSpace() == true && Srv
->ServerFd
!= -1)
643 FD_SET(Srv
->ServerFd
,&wfds
);
644 if (Srv
->In
.ReadSpace() == true && Srv
->ServerFd
!= -1)
645 FD_SET(Srv
->ServerFd
,&rfds
);
652 if (Srv
->In
.WriteSpace() == true && ToFile
== true && FileFD
!= -1)
653 FD_SET(FileFD
,&wfds
);
656 FD_SET(STDIN_FILENO
,&rfds
);
660 FD_SET(FileFD
,&efds
);
661 if (Srv
->ServerFd
!= -1)
662 FD_SET(Srv
->ServerFd
,&efds
);
664 // Figure out the max fd
666 if (MaxFd
< Srv
->ServerFd
)
667 MaxFd
= Srv
->ServerFd
;
674 if ((Res
= select(MaxFd
+1,&rfds
,&wfds
,&efds
,&tv
)) < 0)
675 return _error
->Errno("select","Select failed");
679 _error
->Error("Connection timed out");
680 return ServerDie(Srv
);
683 // Some kind of exception (error) on the sockets, die
684 if ((FileFD
!= -1 && FD_ISSET(FileFD
,&efds
)) ||
685 (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&efds
)))
686 return _error
->Error("Socket Exception");
689 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&rfds
))
692 if (Srv
->In
.Read(Srv
->ServerFd
) == false)
693 return ServerDie(Srv
);
696 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&wfds
))
699 if (Srv
->Out
.Write(Srv
->ServerFd
) == false)
700 return ServerDie(Srv
);
703 // Send data to the file
704 if (FileFD
!= -1 && FD_ISSET(FileFD
,&wfds
))
706 if (Srv
->In
.Write(FileFD
) == false)
707 return _error
->Errno("write","Error writing to output file");
710 // Handle commands from APT
711 if (FD_ISSET(STDIN_FILENO
,&rfds
))
720 // HttpMethod::Flush - Dump the buffer into the file /*{{{*/
721 // ---------------------------------------------------------------------
722 /* This takes the current input buffer from the Server FD and writes it
724 bool HttpMethod::Flush(ServerState
*Srv
)
728 SetNonBlock(File
->Fd(),false);
729 if (Srv
->In
.WriteSpace() == false)
732 while (Srv
->In
.WriteSpace() == true)
734 if (Srv
->In
.Write(File
->Fd()) == false)
735 return _error
->Errno("write","Error writing to file");
736 if (Srv
->In
.IsLimit() == true)
740 if (Srv
->In
.IsLimit() == true || Srv
->Encoding
== ServerState::Closes
)
746 // HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
747 // ---------------------------------------------------------------------
749 bool HttpMethod::ServerDie(ServerState
*Srv
)
751 // Dump the buffer to the file
752 if (Srv
->State
== ServerState::Data
)
754 SetNonBlock(File
->Fd(),false);
755 while (Srv
->In
.WriteSpace() == true)
757 if (Srv
->In
.Write(File
->Fd()) == false)
758 return _error
->Errno("write","Error writing to the file");
761 if (Srv
->In
.IsLimit() == true)
766 // See if this is because the server finished the data stream
767 if (Srv
->In
.IsLimit() == false && Srv
->State
!= ServerState::Header
&&
768 Srv
->Encoding
!= ServerState::Closes
)
771 return _error
->Error("Error reading from server Remote end closed connection");
772 return _error
->Errno("read","Error reading from server");
778 // Nothing left in the buffer
779 if (Srv
->In
.WriteSpace() == false)
782 // We may have got multiple responses back in one packet..
790 // HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
791 // ---------------------------------------------------------------------
792 /* We look at the header data we got back from the server and decide what
796 3 - Unrecoverable error
797 4 - Error with error content page
798 5 - Unrecoverable non-server error (close the connection) */
799 int HttpMethod::DealWithHeaders(FetchResult
&Res
,ServerState
*Srv
)
802 if (Srv
->Result
== 304)
804 unlink(Queue
->DestFile
.c_str());
806 Res
.LastModified
= Queue
->LastModified
;
810 /* We have a reply we dont handle. This should indicate a perm server
812 if (Srv
->Result
< 200 || Srv
->Result
>= 300)
814 _error
->Error("%u %s",Srv
->Result
,Srv
->Code
);
815 if (Srv
->HaveContent
== true)
820 // This is some sort of 2xx 'data follows' reply
821 Res
.LastModified
= Srv
->Date
;
822 Res
.Size
= Srv
->Size
;
826 File
= new FileFd(Queue
->DestFile
,FileFd::WriteAny
);
827 if (_error
->PendingError() == true)
830 FailFile
= Queue
->DestFile
;
832 FailTime
= Srv
->Date
;
834 // Set the expected size
835 if (Srv
->StartPos
>= 0)
837 Res
.ResumePoint
= Srv
->StartPos
;
838 ftruncate(File
->Fd(),Srv
->StartPos
);
841 // Set the start point
842 lseek(File
->Fd(),0,SEEK_END
);
845 Srv
->In
.MD5
= new MD5Summation
;
847 // Fill the MD5 Hash if the file is non-empty (resume)
848 if (Srv
->StartPos
> 0)
850 lseek(File
->Fd(),0,SEEK_SET
);
851 if (Srv
->In
.MD5
->AddFD(File
->Fd(),Srv
->StartPos
) == false)
853 _error
->Errno("read","Problem hashing file");
856 lseek(File
->Fd(),0,SEEK_END
);
859 SetNonBlock(File
->Fd(),true);
863 // HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
864 // ---------------------------------------------------------------------
865 /* This closes and timestamps the open file. This is neccessary to get
866 resume behavoir on user abort */
867 void HttpMethod::SigTerm(int)
876 UBuf
.actime
= FailTime
;
877 UBuf
.modtime
= FailTime
;
878 utime(FailFile
.c_str(),&UBuf
);
883 // HttpMethod::Loop - Main loop /*{{{*/
884 // ---------------------------------------------------------------------
886 int HttpMethod::Loop()
888 signal(SIGTERM
,SigTerm
);
889 signal(SIGINT
,SigTerm
);
891 ServerState
*Server
= 0;
896 if (FailCounter
>= 2)
898 Fail("Massive Server Brain Damage");
902 // We have no commands, wait for some to arrive
905 if (WaitFd(STDIN_FILENO
) == false)
916 // Connect to the server
917 if (Server
== 0 || Server
->Comp(Queue
->Uri
) == false)
920 Server
= new ServerState(Queue
->Uri
,this);
923 // Connnect to the host
924 if (Server
->Open() == false)
931 SendReq(Queue
,Server
->Out
);
933 // Fetch the next URL header data from the server.
934 switch (Server
->RunHeaders())
939 // The header data is bad
942 _error
->Error("Bad header Data");
947 // The server closed a connection during the header get..
952 _error
->DumpErrors();
958 // Decide what to do.
960 Res
.Filename
= Queue
->DestFile
;
961 switch (DealWithHeaders(Res
,Server
))
963 // Ok, the file is Open
969 bool Result
= Server
->RunData();
971 // Close the file, destroy the FD object and timestamp it
979 UBuf
.actime
= Server
->Date
;
980 UBuf
.modtime
= Server
->Date
;
981 utime(Queue
->DestFile
.c_str(),&UBuf
);
983 // Send status to APT
986 Res
.MD5Sum
= Server
->In
.MD5
->Result();
1002 // Hard server error, not found or something
1009 // Hard internal error, kill the connection and fail
1017 // We need to flush the data, the header is like a 404 w/ error text
1022 // Send to content to dev/null
1023 File
= new FileFd("/dev/null",FileFd::WriteExists
);
1031 Fail("Internal error");