]>
git.saurik.com Git - apt.git/blob - methods/http.cc
6418a771922643032742068394073f20f7d91b4f
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: http.cc,v 1.1 1998/11/01 05:30:47 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()
261 if (Proxy
.empty() == false)
263 Port
= ServerName
.Port
;
264 Host
= ServerName
.Host
;
272 if (LastHost
!= Host
)
274 Owner
->Status("Connecting to %s",Host
.c_str());
277 hostent
*Addr
= gethostbyname(Host
.c_str());
279 return _error
->Errno("gethostbyname","Could not lookup host %s",Host
.c_str());
281 LastHostA
= *(in_addr
*)(Addr
->h_addr_list
[0]);
284 Owner
->Status("Connecting to %s (%s)",Host
.c_str(),inet_ntoa(LastHostA
));
287 if ((ServerFd
= socket(AF_INET
,SOCK_STREAM
,0)) < 0)
288 return _error
->Errno("socket","Could not create a socket");
290 // Connect to the server
291 struct sockaddr_in server
;
292 server
.sin_family
= AF_INET
;
293 server
.sin_port
= htons(Port
);
294 server
.sin_addr
= LastHostA
;
295 if (connect(ServerFd
,(sockaddr
*)&server
,sizeof(server
)) < 0)
296 return _error
->Errno("socket","Could not create a socket");
298 SetNonBlock(ServerFd
,true);
302 // ServerState::Close - Close a connection to the server /*{{{*/
303 // ---------------------------------------------------------------------
305 bool ServerState::Close()
314 // ServerState::RunHeaders - Get the headers before the data /*{{{*/
315 // ---------------------------------------------------------------------
317 bool ServerState::RunHeaders()
321 Owner
->Status("Waiting for file");
334 if (In
.WriteTillEl(Data
) == false)
337 for (string::const_iterator I
= Data
.begin(); I
< Data
.end(); I
++)
339 string::const_iterator J
= I
;
340 for (; J
!= Data
.end() && *J
!= '\n' && *J
!= '\r';J
++);
341 if (HeaderLine(string(I
,J
-I
)) == false)
347 while (Owner
->Go(false,this) == true);
352 // ServerState::RunData - Transfer the data from the socket /*{{{*/
353 // ---------------------------------------------------------------------
355 bool ServerState::RunData()
359 // Chunked transfer encoding is fun..
360 if (Encoding
== Chunked
)
364 // Grab the block size
370 if (In
.WriteTillEl(Data
,true) == true)
373 while ((Last
= Owner
->Go(false,this)) == true);
378 // See if we are done
379 unsigned long Len
= strtol(Data
.c_str(),0,16);
384 // We have to remove the entity trailer
388 if (In
.WriteTillEl(Data
,true) == true && Data
.length() <= 2)
391 while ((Last
= Owner
->Go(false,this)) == true);
397 // Transfer the block
399 while (Owner
->Go(true,this) == true)
400 if (In
.IsLimit() == true)
404 if (In
.IsLimit() == false)
407 // The server sends an extra new line before the next block specifier..
412 if (In
.WriteTillEl(Data
,true) == true)
415 while ((Last
= Owner
->Go(false,this)) == true);
422 /* Closes encoding is used when the server did not specify a size, the
423 loss of the connection means we are done */
424 if (Encoding
== Closes
)
427 In
.Limit(Size
- StartPos
);
429 // Just transfer the whole block.
432 if (In
.IsLimit() == false)
438 while (Owner
->Go(true,this) == true);
441 return Owner
->Flush(this);
444 // ServerState::HeaderLine - Process a header line /*{{{*/
445 // ---------------------------------------------------------------------
447 bool ServerState::HeaderLine(string Line
)
449 if (Line
.empty() == true)
452 // The http server might be trying to do something evil.
453 if (Line
.length() >= MAXLEN
)
454 return _error
->Error("Got a single header line over %u chars",MAXLEN
);
456 string::size_type Pos
= Line
.find(' ');
457 if (Pos
== string::npos
|| Pos
+1 > Line
.length())
458 return _error
->Error("Bad header line");
460 string Tag
= string(Line
,0,Pos
);
461 string Val
= string(Line
,Pos
+1);
463 if (stringcasecmp(Tag
,"HTTP") == 0)
465 // Evil servers return no version
468 if (sscanf(Line
.c_str(),"HTTP/%u.%u %u %[^\n]",&Major
,&Minor
,
470 return _error
->Error("The http server sent an invalid reply header");
476 if (sscanf(Line
.c_str(),"HTTP %u %[^\n]",&Result
,Code
) != 2)
477 return _error
->Error("The http server sent an invalid reply header");
483 if (stringcasecmp(Tag
,"Content-Length:"))
485 if (Encoding
== Closes
)
488 // The length is already set from the Content-Range header
492 if (sscanf(Val
.c_str(),"%lu",&Size
) != 1)
493 return _error
->Error("The http server sent an invalid Content-Length header");
497 if (stringcasecmp(Tag
,"Content-Range:"))
499 if (sscanf(Val
.c_str(),"bytes %lu-%*u/%lu",&StartPos
,&Size
) != 2)
500 return _error
->Error("The http server sent an invalid Content-Range header");
501 if ((unsigned)StartPos
> Size
)
502 return _error
->Error("This http server has broken range support");
506 if (stringcasecmp(Tag
,"Transfer-Encoding:"))
508 if (stringcasecmp(Val
,"chunked"))
513 if (stringcasecmp(Tag
,"Last-Modified:"))
515 if (StrToTime(Val
,Date
) == false)
516 return _error
->Error("Unknown date format");
524 // HttpMethod::SendReq - Send the HTTP request /*{{{*/
525 // ---------------------------------------------------------------------
526 /* This places the http request in the outbound buffer */
527 void HttpMethod::SendReq(FetchItem
*Itm
,CircleBuf
&Out
)
531 // The HTTP server expects a hostname with a trailing :port
533 string ProperHost
= Uri
.Host
;
536 sprintf(Buf
,":%u",Uri
.Port
);
541 if (Proxy
.empty() == true)
542 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
543 Uri
.Path
.c_str(),ProperHost
.c_str());
545 sprintf(Buf
,"GET %s HTTP/1.1\r\nHost: %s\r\n",
546 Itm
->Uri
.c_str(),ProperHost
.c_str());
549 // Check for a partial file
551 if (stat(Itm
->DestFile
.c_str(),&SBuf
) >= 0 && SBuf
.st_size
> 0)
553 // In this case we send an if-range query with a range header
554 sprintf(Buf
,"Range: bytes=%li-\r\nIf-Range: %s\r\n",SBuf
.st_size
- 1,
555 TimeRFC1123(SBuf
.st_mtime
).c_str());
560 if (Itm
->LastModified
!= 0)
562 sprintf(Buf
,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm
->LastModified
).c_str());
567 /* if (ProxyAuth.empty() == false)
568 Req += string("Proxy-Authorization: Basic ") + Base64Encode(ProxyAuth) + "\r\n";*/
570 Req
+= "User-Agent: Debian APT-HTTP/1.2\r\n\r\n";
574 // HttpMethod::Go - Run a single loop /*{{{*/
575 // ---------------------------------------------------------------------
576 /* This runs the select loop over the server FDs, Output file FDs and
578 bool HttpMethod::Go(bool ToFile
,ServerState
*Srv
)
580 // Server has closed the connection
581 if (Srv
->ServerFd
== -1 && Srv
->In
.WriteSpace() == false)
584 fd_set rfds
,wfds
,efds
;
590 if (Srv
->Out
.WriteSpace() == true && Srv
->ServerFd
!= -1)
591 FD_SET(Srv
->ServerFd
,&wfds
);
592 if (Srv
->In
.ReadSpace() == true && Srv
->ServerFd
!= -1)
593 FD_SET(Srv
->ServerFd
,&rfds
);
600 if (Srv
->In
.WriteSpace() == true && ToFile
== true && FileFD
!= -1)
601 FD_SET(FileFD
,&wfds
);
604 FD_SET(STDIN_FILENO
,&rfds
);
608 FD_SET(FileFD
,&efds
);
609 if (Srv
->ServerFd
!= -1)
610 FD_SET(Srv
->ServerFd
,&efds
);
612 // Figure out the max fd
614 if (MaxFd
< Srv
->ServerFd
)
615 MaxFd
= Srv
->ServerFd
;
622 if ((Res
= select(MaxFd
+1,&rfds
,&wfds
,&efds
,&tv
)) < 0)
623 return _error
->Errno("select","Select failed");
627 _error
->Error("Connection timed out");
628 return ServerDie(Srv
);
631 // Some kind of exception (error) on the sockets, die
632 if ((FileFD
!= -1 && FD_ISSET(FileFD
,&efds
)) ||
633 (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&efds
)))
634 return _error
->Error("Socket Exception");
637 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&rfds
))
640 if (Srv
->In
.Read(Srv
->ServerFd
) == false)
641 return ServerDie(Srv
);
644 if (Srv
->ServerFd
!= -1 && FD_ISSET(Srv
->ServerFd
,&wfds
))
647 if (Srv
->Out
.Write(Srv
->ServerFd
) == false)
648 return ServerDie(Srv
);
651 // Send data to the file
652 if (FileFD
!= -1 && FD_ISSET(FileFD
,&wfds
))
654 if (Srv
->In
.Write(FileFD
) == false)
655 return _error
->Errno("write","Error writing to output file");
658 // Handle commands from APT
659 if (FD_ISSET(STDIN_FILENO
,&rfds
))
668 // HttpMethod::Flush - Dump the buffer into the file /*{{{*/
669 // ---------------------------------------------------------------------
670 /* This takes the current input buffer from the Server FD and writes it
672 bool HttpMethod::Flush(ServerState
*Srv
)
676 SetNonBlock(File
->Fd(),false);
677 if (Srv
->In
.WriteSpace() == false)
680 while (Srv
->In
.WriteSpace() == true)
682 if (Srv
->In
.Write(File
->Fd()) == false)
683 return _error
->Errno("write","Error writing to file");
686 if (Srv
->In
.IsLimit() == true || Srv
->Encoding
== ServerState::Closes
)
692 // HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
693 // ---------------------------------------------------------------------
695 bool HttpMethod::ServerDie(ServerState
*Srv
)
697 // Dump the buffer to the file
698 if (Srv
->State
== ServerState::Data
)
700 SetNonBlock(File
->Fd(),false);
701 while (Srv
->In
.WriteSpace() == true)
703 if (Srv
->In
.Write(File
->Fd()) == false)
704 return _error
->Errno("write","Error writing to the file");
708 // See if this is because the server finished the data stream
709 if (Srv
->In
.IsLimit() == false && Srv
->State
!= ServerState::Header
&&
710 Srv
->Encoding
!= ServerState::Closes
)
713 return _error
->Error("Error reading from server Remote end closed connection");
714 return _error
->Errno("read","Error reading from server");
720 // Nothing left in the buffer
721 if (Srv
->In
.WriteSpace() == false)
724 // We may have got multiple responses back in one packet..
732 // HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
733 // ---------------------------------------------------------------------
734 /* We look at the header data we got back from the server and decide what
738 3 - Unrecoverable error */
739 int HttpMethod::DealWithHeaders(FetchResult
&Res
,ServerState
*Srv
)
742 if (Srv
->Result
== 304)
744 unlink(Queue
->DestFile
.c_str());
746 Res
.LastModified
= Queue
->LastModified
;
750 /* We have a reply we dont handle. This should indicate a perm server
752 if (Srv
->Result
< 200 || Srv
->Result
>= 300)
754 _error
->Error("%u %s",Srv
->Result
,Srv
->Code
);
758 // This is some sort of 2xx 'data follows' reply
759 Res
.LastModified
= Srv
->Date
;
760 Res
.Size
= Srv
->Size
;
764 File
= new FileFd(Queue
->DestFile
,FileFd::WriteAny
);
765 if (_error
->PendingError() == true)
768 // Set the expected size
769 if (Srv
->StartPos
>= 0)
771 Res
.ResumePoint
= Srv
->StartPos
;
772 ftruncate(File
->Fd(),Srv
->StartPos
);
775 // Set the start point
776 lseek(File
->Fd(),0,SEEK_END
);
779 Srv
->In
.MD5
= new MD5Summation
;
781 // Fill the MD5 Hash if the file is non-empty (resume)
782 if (Srv
->StartPos
> 0)
784 lseek(File
->Fd(),0,SEEK_SET
);
785 if (Srv
->In
.MD5
->AddFD(File
->Fd(),Srv
->StartPos
) == false)
787 _error
->Errno("read","Problem hashing file");
790 lseek(File
->Fd(),0,SEEK_END
);
793 SetNonBlock(File
->Fd(),true);
797 // HttpMethod::Loop /*{{{*/
798 // ---------------------------------------------------------------------
800 int HttpMethod::Loop()
802 ServerState
*Server
= 0;
806 // We have no commands, wait for some to arrive
809 if (WaitFd(STDIN_FILENO
) == false)
820 // Connect to the server
821 if (Server
== 0 || Server
->Comp(Queue
->Uri
) == false)
824 Server
= new ServerState(Queue
->Uri
,this);
827 // Connnect to the host
828 if (Server
->Open() == false)
835 SendReq(Queue
,Server
->In
);
837 // Handle the header data
838 if (Server
->RunHeaders() == false)
844 // Decide what to do.
846 switch (DealWithHeaders(Res
,Server
))
848 // Ok, the file is Open
854 if (Server
->RunData() == false)
857 Res
.MD5Sum
= Srv
->In
.MD5
->Result();
870 // Hard server error, not found or something
878 Fail("Internal error");