]>
git.saurik.com Git - apt.git/blob - methods/http.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: http.cc,v 1.2 1998/11/01 08:07:13 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>
42 #include <netinet/in.h>
43 #include <sys/socket.h>
44 #include <arpa/inet.h>
50 // CircleBuf::CircleBuf - Circular input buffer /*{{{*/
51 // ---------------------------------------------------------------------
53 CircleBuf::CircleBuf(unsigned long Size
) : Size(Size
), MD5(0)
55 Buf
= new unsigned char[Size
];
59 // CircleBuf::Reset - Reset to the default state /*{{{*/
60 // ---------------------------------------------------------------------
62 void CircleBuf::Reset()
67 MaxGet
= (unsigned int)-1;
72 MD5
= new MD5Summation
;
76 // CircleBuf::Read - Read from a FD into the circular buffer /*{{{*/
77 // ---------------------------------------------------------------------
78 /* This fills up the buffer with as much data as is in the FD, assuming it
80 bool CircleBuf::Read(int Fd
)
84 // Woops, buffer is full
85 if (InP
- OutP
== Size
)
88 // Write the buffer segment
90 Res
= read(Fd
,Buf
+ (InP%Size
),LeftRead());
102 gettimeofday(&Start
,0);
107 // CircleBuf::Read - Put the string into the buffer /*{{{*/
108 // ---------------------------------------------------------------------
109 /* This will hold the string in and fill the buffer with it as it empties */
110 bool CircleBuf::Read(string Data
)
117 // CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/
118 // ---------------------------------------------------------------------
120 void CircleBuf::FillOut()
122 if (OutQueue
.empty() == true)
126 // Woops, buffer is full
127 if (InP
- OutP
== Size
)
130 // Write the buffer segment
131 unsigned long Sz
= LeftRead();
132 if (OutQueue
.length() - StrPos
< Sz
)
133 Sz
= OutQueue
.length() - StrPos
;
134 memcpy(Buf
+ (InP%Size
),OutQueue
.begin() + StrPos
,Sz
);
139 if (OutQueue
.length() == StrPos
)
148 // CircleBuf::Write - Write from the buffer into a FD /*{{{*/
149 // ---------------------------------------------------------------------
150 /* This empties the buffer into the FD. */
151 bool CircleBuf::Write(int Fd
)
157 // Woops, buffer is empty
164 // Write the buffer segment
166 Res
= write(Fd
,Buf
+ (OutP%Size
),LeftWrite());
179 MD5
->Add(Buf
+ (OutP%Size
),Res
);
185 // CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/
186 // ---------------------------------------------------------------------
187 /* This copies till the first empty line */
188 bool CircleBuf::WriteTillEl(string
&Data
,bool Single
)
190 // We cheat and assume it is unneeded to have more than one buffer load
191 for (unsigned long I
= OutP
; I
< InP
; I
++)
193 if (Buf
[I%Size
] != '\n')
195 for (I
++; I
< InP
&& Buf
[I%Size
] == '\r'; I
++);
199 if (Buf
[I%Size
] != '\n')
201 for (I
++; I
< InP
&& Buf
[I%Size
] == '\r'; I
++);
210 unsigned long Sz
= LeftWrite();
213 if (I
- OutP
< LeftWrite())
215 Data
+= string((char *)(Buf
+ (OutP%Size
)),Sz
);
223 // CircleBuf::Stats - Print out stats information /*{{{*/
224 // ---------------------------------------------------------------------
226 void CircleBuf::Stats()
232 gettimeofday(&Stop
,0);
233 /* float Diff = Stop.tv_sec - Start.tv_sec +
234 (float)(Stop.tv_usec - Start.tv_usec)/1000000;
235 clog << "Got " << InP << " in " << Diff << " at " << InP/Diff << endl;*/
239 // ServerState::ServerState - Constructor /*{{{*/
240 // ---------------------------------------------------------------------
242 ServerState::ServerState(URI Srv
,HttpMethod
*Owner
) : Owner(Owner
),
243 In(64*1024), Out(1*1024),
249 // ServerState::Open - Open a connection to the server /*{{{*/
250 // ---------------------------------------------------------------------
251 /* This opens a connection to the server. */
254 bool ServerState::Open()
256 // Use the already open connection if possible.
265 if (Proxy
.empty() == true)
267 if (ServerName
.Port
!= 0)
268 Port
= ServerName
.Port
;
269 Host
= ServerName
.Host
;
278 if (LastHost
!= Host
)
280 Owner
->Status("Connecting to %s",Host
.c_str());
283 hostent
*Addr
= gethostbyname(Host
.c_str());
285 return _error
->Error("Could not resolve '%s'",Host
.c_str());
287 LastHostA
= *(in_addr
*)(Addr
->h_addr_list
[0]);
290 Owner
->Status("Connecting to %s (%s)",Host
.c_str(),inet_ntoa(LastHostA
));
293 if ((ServerFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
294 return _error
->Errno("socket","Could not create a socket");
296 // Connect to the server
297 struct sockaddr_in server
;
298 server
.sin_family
= AF_INET
;
299 server
.sin_port
= htons(Port
);
300 server
.sin_addr
= LastHostA
;
301 if (connect(ServerFd
,(sockaddr
*)&server
,sizeof(server
)) < 0)
302 return _error
->Errno("socket","Could not create a socket");
304 SetNonBlock(ServerFd
,true);
308 // ServerState::Close - Close a connection to the server /*{{{*/
309 // ---------------------------------------------------------------------
311 bool ServerState::Close()
320 // ServerState::RunHeaders - Get the headers before the data /*{{{*/
321 // ---------------------------------------------------------------------
322 /* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
323 parse error occured */
324 int ServerState::RunHeaders()
328 Owner
->Status("Waiting for file");
342 if (In
.WriteTillEl(Data
) == false)
345 for (string::const_iterator I
= Data
.begin(); I
< Data
.end(); I
++)
347 string::const_iterator J
= I
;
348 for (; J
!= Data
.end() && *J
!= '\n' && *J
!= '\r';J
++);
349 if (HeaderLine(string(I
,J
-I
)) == false)
355 while (Owner
->Go(false,this) == true);
360 // ServerState::RunData - Transfer the data from the socket /*{{{*/
361 // ---------------------------------------------------------------------
363 bool ServerState::RunData()
367 // Chunked transfer encoding is fun..
368 if (Encoding
== Chunked
)
372 // Grab the block size
378 if (In
.WriteTillEl(Data
,true) == true)
381 while ((Last
= Owner
->Go(false,this)) == true);
386 // See if we are done
387 unsigned long Len
= strtol(Data
.c_str(),0,16);
392 // We have to remove the entity trailer
396 if (In
.WriteTillEl(Data
,true) == true && Data
.length() <= 2)
399 while ((Last
= Owner
->Go(false,this)) == true);
405 // Transfer the block
407 while (Owner
->Go(true,this) == true)
408 if (In
.IsLimit() == true)
412 if (In
.IsLimit() == false)
415 // The server sends an extra new line before the next block specifier..
420 if (In
.WriteTillEl(Data
,true) == true)
423 while ((Last
= Owner
->Go(false,this)) == true);
430 /* Closes encoding is used when the server did not specify a size, the
431 loss of the connection means we are done */
432 if (Encoding
== Closes
)
435 In
.Limit(Size
- StartPos
);
437 // Just transfer the whole block.
440 if (In
.IsLimit() == false)
446 while (Owner
->Go(true,this) == true);
449 return Owner
->Flush(this);
452 // ServerState::HeaderLine - Process a header line /*{{{*/
453 // ---------------------------------------------------------------------
455 bool ServerState::HeaderLine(string Line
)
457 if (Line
.empty() == true)
460 // The http server might be trying to do something evil.
461 if (Line
.length() >= MAXLEN
)
462 return _error
->Error("Got a single header line over %u chars",MAXLEN
);
464 string::size_type Pos
= Line
.find(' ');
465 if (Pos
== string::npos
|| Pos
+1 > Line
.length())
466 return _error
->Error("Bad header line");
468 string Tag
= string(Line
,0,Pos
);
469 string Val
= string(Line
,Pos
+1);
471 if (stringcasecmp(Tag
.begin(),Tag
.begin()+4,"HTTP") == 0)
473 // Evil servers return no version
476 if (sscanf(Line
.c_str(),"HTTP/%u.%u %u %[^\n]",&Major
,&Minor
,
478 return _error
->Error("The http server sent an invalid reply header");
484 if (sscanf(Line
.c_str(),"HTTP %u %[^\n]",&Result
,Code
) != 2)
485 return _error
->Error("The http server sent an invalid reply header");
491 if (stringcasecmp(Tag
,"Content-Length:") == 0)
493 if (Encoding
== Closes
)
497 // The length is already set from the Content-Range header
501 if (sscanf(Val
.c_str(),"%lu",&Size
) != 1)
502 return _error
->Error("The http server sent an invalid Content-Length header");
506 if (stringcasecmp(Tag
,"Content-Type:") == 0)
512 if (stringcasecmp(Tag
,"Content-Range:") == 0)
516 if (sscanf(Val
.c_str(),"bytes %lu-%*u/%lu",&StartPos
,&Size
) != 2)
517 return _error
->Error("The http server sent an invalid Content-Range header");
518 if ((unsigned)StartPos
> Size
)
519 return _error
->Error("This http server has broken range support");
523 if (stringcasecmp(Tag
,"Transfer-Encoding:") == 0)
526 if (stringcasecmp(Val
,"chunked") == 0)
532 if (stringcasecmp(Tag
,"Last-Modified:") == 0)
534 if (StrToTime(Val
,Date
) == false)
535 return _error
->Error("Unknown date format");
543 // HttpMethod::SendReq - Send the HTTP request /*{{{*/
544 // ---------------------------------------------------------------------
545 /* This places the http request in the outbound buffer */
546 void HttpMethod::SendReq(FetchItem
*Itm
,CircleBuf
&Out
)
550 // The HTTP server expects a hostname with a trailing :port
552 string ProperHost
= Uri
.Host
;
555 sprintf(Buf
,":%u",Uri
.Port
);
560 if (Proxy
.empty() == true)
561 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
562 Uri
.Path
.c_str(),ProperHost
.c_str());
564 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\n",
565 Itm
->Uri
.c_str(),ProperHost
.c_str());
568 // Check for a partial file
570 if (stat(Itm
->DestFile
.c_str(),&SBuf
) >= 0 && SBuf
.st_size
> 0)
572 // In this case we send an if-range query with a range header
573 sprintf(Buf
,"Range: bytes=%li-\r\nIf-Range: %s\r\n",SBuf
.st_size
- 1,
574 TimeRFC1123(SBuf
.st_mtime
).c_str());
579 if (Itm
->LastModified
!= 0)
581 sprintf(Buf
,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm
->LastModified
).c_str());
586 /* if (ProxyAuth.empty() == false)
587 Req += string("Proxy-Authorization: Basic ") + Base64Encode(ProxyAuth) + "\r\n";*/
589 Req
+= "User-Agent: Debian APT-HTTP/1.2\r\n\r\n";
590 // cout << Req << endl;
595 // HttpMethod::Go - Run a single loop /*{{{*/
596 // ---------------------------------------------------------------------
597 /* This runs the select loop over the server FDs, Output file FDs and
599 bool HttpMethod::Go(bool ToFile
,ServerState
*Srv
)
601 // Server has closed the connection
602 if (Srv
->ServerFd
== -1 && Srv
->In
.WriteSpace() == false)
605 fd_set rfds
,wfds
,efds
;
611 if (Srv
->Out
.WriteSpace() == true && Srv
->ServerFd
!= -1)
612 FD_SET(Srv
->ServerFd
,&wfds
);
613 if (Srv
->In
.ReadSpace() == true && Srv
->ServerFd
!= -1)
614 FD_SET(Srv
->ServerFd
,&rfds
);
621 if (Srv
->In
.WriteSpace() == true && ToFile
== true && FileFD
!= -1)
622 FD_SET(FileFD
,&wfds
);
625 FD_SET(STDIN_FILENO
,&rfds
);
629 FD_SET(FileFD
,&efds
);
630 if (Srv
->ServerFd
!= -1)
631 FD_SET(Srv
->ServerFd
,&efds
);
633 // Figure out the max fd
635 if (MaxFd
< Srv
->ServerFd
)
636 MaxFd
= Srv
->ServerFd
;
643 if ((Res
= select(MaxFd
+1,&rfds
,&wfds
,&efds
,&tv
)) < 0)
644 return _error
->Errno("select","Select failed");
648 _error
->Error("Connection timed out");
649 return ServerDie(Srv
);
652 // Some kind of exception (error) on the sockets, die
653 if ((FileFD
!= -1 && FD_ISSET(FileFD
,&efds
)) ||
654 (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&efds
)))
655 return _error
->Error("Socket Exception");
658 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&rfds
))
661 if (Srv
->In
.Read(Srv
->ServerFd
) == false)
662 return ServerDie(Srv
);
665 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&wfds
))
668 if (Srv
->Out
.Write(Srv
->ServerFd
) == false)
669 return ServerDie(Srv
);
672 // Send data to the file
673 if (FileFD
!= -1 && FD_ISSET(FileFD
,&wfds
))
675 if (Srv
->In
.Write(FileFD
) == false)
676 return _error
->Errno("write","Error writing to output file");
679 // Handle commands from APT
680 if (FD_ISSET(STDIN_FILENO
,&rfds
))
689 // HttpMethod::Flush - Dump the buffer into the file /*{{{*/
690 // ---------------------------------------------------------------------
691 /* This takes the current input buffer from the Server FD and writes it
693 bool HttpMethod::Flush(ServerState
*Srv
)
697 SetNonBlock(File
->Fd(),false);
698 if (Srv
->In
.WriteSpace() == false)
701 while (Srv
->In
.WriteSpace() == true)
703 if (Srv
->In
.Write(File
->Fd()) == false)
704 return _error
->Errno("write","Error writing to file");
705 if (Srv
->In
.IsLimit() == true)
709 if (Srv
->In
.IsLimit() == true || Srv
->Encoding
== ServerState::Closes
)
715 // HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
716 // ---------------------------------------------------------------------
718 bool HttpMethod::ServerDie(ServerState
*Srv
)
720 // Dump the buffer to the file
721 if (Srv
->State
== ServerState::Data
)
723 SetNonBlock(File
->Fd(),false);
724 while (Srv
->In
.WriteSpace() == true)
726 if (Srv
->In
.Write(File
->Fd()) == false)
727 return _error
->Errno("write","Error writing to the file");
730 if (Srv
->In
.IsLimit() == true)
735 // See if this is because the server finished the data stream
736 if (Srv
->In
.IsLimit() == false && Srv
->State
!= ServerState::Header
&&
737 Srv
->Encoding
!= ServerState::Closes
)
740 return _error
->Error("Error reading from server Remote end closed connection");
741 return _error
->Errno("read","Error reading from server");
747 // Nothing left in the buffer
748 if (Srv
->In
.WriteSpace() == false)
751 // We may have got multiple responses back in one packet..
759 // HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
760 // ---------------------------------------------------------------------
761 /* We look at the header data we got back from the server and decide what
765 3 - Unrecoverable error
766 4 - Error with error content page */
767 int HttpMethod::DealWithHeaders(FetchResult
&Res
,ServerState
*Srv
)
770 if (Srv
->Result
== 304)
772 unlink(Queue
->DestFile
.c_str());
774 Res
.LastModified
= Queue
->LastModified
;
778 /* We have a reply we dont handle. This should indicate a perm server
780 if (Srv
->Result
< 200 || Srv
->Result
>= 300)
782 _error
->Error("%u %s",Srv
->Result
,Srv
->Code
);
783 if (Srv
->HaveContent
== true)
788 // This is some sort of 2xx 'data follows' reply
789 Res
.LastModified
= Srv
->Date
;
790 Res
.Size
= Srv
->Size
;
794 File
= new FileFd(Queue
->DestFile
,FileFd::WriteAny
);
795 if (_error
->PendingError() == true)
798 // Set the expected size
799 if (Srv
->StartPos
>= 0)
801 Res
.ResumePoint
= Srv
->StartPos
;
802 ftruncate(File
->Fd(),Srv
->StartPos
);
805 // Set the start point
806 lseek(File
->Fd(),0,SEEK_END
);
809 Srv
->In
.MD5
= new MD5Summation
;
811 // Fill the MD5 Hash if the file is non-empty (resume)
812 if (Srv
->StartPos
> 0)
814 lseek(File
->Fd(),0,SEEK_SET
);
815 if (Srv
->In
.MD5
->AddFD(File
->Fd(),Srv
->StartPos
) == false)
817 _error
->Errno("read","Problem hashing file");
820 lseek(File
->Fd(),0,SEEK_END
);
823 SetNonBlock(File
->Fd(),true);
827 // HttpMethod::Loop /*{{{*/
828 // ---------------------------------------------------------------------
830 int HttpMethod::Loop()
832 ServerState
*Server
= 0;
837 if (FailCounter
>= 2)
839 Fail("Massive Server Brain Damage");
843 // We have no commands, wait for some to arrive
846 if (WaitFd(STDIN_FILENO
) == false)
857 // Connect to the server
858 if (Server
== 0 || Server
->Comp(Queue
->Uri
) == false)
861 Server
= new ServerState(Queue
->Uri
,this);
864 // Connnect to the host
865 if (Server
->Open() == false)
872 SendReq(Queue
,Server
->Out
);
874 // Fetch the next URL header data from the server.
875 switch (Server
->RunHeaders())
880 // The header data is bad
883 _error
->Error("Bad header Data");
888 // The server closed a connection during the header get..
893 _error
->DumpErrors();
899 // Decide what to do.
901 switch (DealWithHeaders(Res
,Server
))
903 // Ok, the file is Open
909 if (Server
->RunData() == false)
913 Res
.MD5Sum
= Server
->In
.MD5
->Result();
929 // Hard server error, not found or something
936 // We need to flush the data, the header is like a 404 w/ error text
941 // Send to content to dev/null
942 File
= new FileFd("/dev/null",FileFd::WriteExists
);
950 Fail("Internal error");