]> git.saurik.com Git - apt.git/blame - methods/http.cc
Multiple different versions support
[apt.git] / methods / http.cc
CommitLineData
be4401bf
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
c901051d 3// $Id: http.cc,v 1.30 1999/04/04 02:02:04 jgg Exp $
be4401bf
AL
4/* ######################################################################
5
6 HTTP Aquire Method - This is the HTTP aquire method for APT.
7
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.
12
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.
18
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.
26
27 ##################################################################### */
28 /*}}}*/
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>
34
35#include <sys/stat.h>
36#include <sys/time.h>
37#include <utime.h>
38#include <unistd.h>
492f957a 39#include <signal.h>
be4401bf 40#include <stdio.h>
65a1e968 41#include <errno.h>
be4401bf
AL
42
43// Internet stuff
44#include <netinet/in.h>
45#include <sys/socket.h>
46#include <arpa/inet.h>
47#include <netdb.h>
48
49#include "http.h"
50 /*}}}*/
51
492f957a
AL
52string HttpMethod::FailFile;
53int HttpMethod::FailFd = -1;
54time_t HttpMethod::FailTime = 0;
3000ccea
AL
55unsigned long PipelineDepth = 10;
56unsigned long TimeOut = 120;
492f957a 57
be4401bf
AL
58// CircleBuf::CircleBuf - Circular input buffer /*{{{*/
59// ---------------------------------------------------------------------
60/* */
61CircleBuf::CircleBuf(unsigned long Size) : Size(Size), MD5(0)
62{
63 Buf = new unsigned char[Size];
64 Reset();
65}
66 /*}}}*/
67// CircleBuf::Reset - Reset to the default state /*{{{*/
68// ---------------------------------------------------------------------
69/* */
70void CircleBuf::Reset()
71{
72 InP = 0;
73 OutP = 0;
74 StrPos = 0;
75 MaxGet = (unsigned int)-1;
76 OutQueue = string();
77 if (MD5 != 0)
78 {
79 delete MD5;
80 MD5 = new MD5Summation;
81 }
82};
83 /*}}}*/
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
87 is non-blocking.. */
88bool CircleBuf::Read(int Fd)
89{
90 while (1)
91 {
92 // Woops, buffer is full
93 if (InP - OutP == Size)
94 return true;
95
96 // Write the buffer segment
97 int Res;
98 Res = read(Fd,Buf + (InP%Size),LeftRead());
99
100 if (Res == 0)
101 return false;
102 if (Res < 0)
103 {
104 if (errno == EAGAIN)
105 return true;
106 return false;
107 }
108
109 if (InP == 0)
110 gettimeofday(&Start,0);
111 InP += Res;
112 }
113}
114 /*}}}*/
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 */
118bool CircleBuf::Read(string Data)
119{
120 OutQueue += Data;
121 FillOut();
122 return true;
123}
124 /*}}}*/
125// CircleBuf::FillOut - Fill the buffer from the output queue /*{{{*/
126// ---------------------------------------------------------------------
127/* */
128void CircleBuf::FillOut()
129{
130 if (OutQueue.empty() == true)
131 return;
132 while (1)
133 {
134 // Woops, buffer is full
135 if (InP - OutP == Size)
136 return;
137
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);
143
144 // Advance
145 StrPos += Sz;
146 InP += Sz;
147 if (OutQueue.length() == StrPos)
148 {
149 StrPos = 0;
150 OutQueue = "";
151 return;
152 }
153 }
154}
155 /*}}}*/
156// CircleBuf::Write - Write from the buffer into a FD /*{{{*/
157// ---------------------------------------------------------------------
158/* This empties the buffer into the FD. */
159bool CircleBuf::Write(int Fd)
160{
161 while (1)
162 {
163 FillOut();
164
165 // Woops, buffer is empty
166 if (OutP == InP)
167 return true;
168
169 if (OutP == MaxGet)
170 return true;
171
172 // Write the buffer segment
173 int Res;
174 Res = write(Fd,Buf + (OutP%Size),LeftWrite());
175
176 if (Res == 0)
177 return false;
178 if (Res < 0)
179 {
180 if (errno == EAGAIN)
181 return true;
182
183 return false;
184 }
185
186 if (MD5 != 0)
187 MD5->Add(Buf + (OutP%Size),Res);
188
189 OutP += Res;
190 }
191}
192 /*}}}*/
193// CircleBuf::WriteTillEl - Write from the buffer to a string /*{{{*/
194// ---------------------------------------------------------------------
195/* This copies till the first empty line */
196bool CircleBuf::WriteTillEl(string &Data,bool Single)
197{
198 // We cheat and assume it is unneeded to have more than one buffer load
199 for (unsigned long I = OutP; I < InP; I++)
200 {
201 if (Buf[I%Size] != '\n')
202 continue;
203 for (I++; I < InP && Buf[I%Size] == '\r'; I++);
204
205 if (Single == false)
206 {
207 if (Buf[I%Size] != '\n')
208 continue;
209 for (I++; I < InP && Buf[I%Size] == '\r'; I++);
210 }
211
212 if (I > InP)
213 I = InP;
214
215 Data = "";
216 while (OutP < I)
217 {
218 unsigned long Sz = LeftWrite();
219 if (Sz == 0)
220 return false;
221 if (I - OutP < LeftWrite())
222 Sz = I - OutP;
223 Data += string((char *)(Buf + (OutP%Size)),Sz);
224 OutP += Sz;
225 }
226 return true;
227 }
228 return false;
229}
230 /*}}}*/
231// CircleBuf::Stats - Print out stats information /*{{{*/
232// ---------------------------------------------------------------------
233/* */
234void CircleBuf::Stats()
235{
236 if (InP == 0)
237 return;
238
239 struct timeval Stop;
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;*/
244}
245 /*}}}*/
246
247// ServerState::ServerState - Constructor /*{{{*/
248// ---------------------------------------------------------------------
249/* */
250ServerState::ServerState(URI Srv,HttpMethod *Owner) : Owner(Owner),
3000ccea 251 In(64*1024), Out(4*1024),
be4401bf
AL
252 ServerName(Srv)
253{
254 Reset();
255}
256 /*}}}*/
257// ServerState::Open - Open a connection to the server /*{{{*/
258// ---------------------------------------------------------------------
259/* This opens a connection to the server. */
260string LastHost;
261in_addr LastHostA;
262bool ServerState::Open()
263{
92e889c8
AL
264 // Use the already open connection if possible.
265 if (ServerFd != -1)
266 return true;
267
be4401bf 268 Close();
492f957a
AL
269 In.Reset();
270 Out.Reset();
271
272 // Determine the proxy setting
52e7839a 273 if (getenv("http_proxy") == 0)
492f957a 274 {
352c2768
AL
275 string DefProxy = _config->Find("Acquire::http::Proxy");
276 string SpecificProxy = _config->Find("Acquire::http::Proxy::" + ServerName.Host);
277 if (SpecificProxy.empty() == false)
278 {
279 if (SpecificProxy == "DIRECT")
280 Proxy = "";
281 else
282 Proxy = SpecificProxy;
283 }
492f957a 284 else
352c2768
AL
285 Proxy = DefProxy;
286 }
492f957a 287 else
352c2768
AL
288 Proxy = getenv("http_proxy");
289
492f957a 290 // Determine what host and port to use based on the proxy settings
92e889c8 291 int Port = 80;
492f957a 292 string Host;
92e889c8 293 if (Proxy.empty() == true)
be4401bf 294 {
92e889c8
AL
295 if (ServerName.Port != 0)
296 Port = ServerName.Port;
be4401bf
AL
297 Host = ServerName.Host;
298 }
299 else
300 {
92e889c8
AL
301 if (Proxy.Port != 0)
302 Port = Proxy.Port;
be4401bf
AL
303 Host = Proxy.Host;
304 }
305
492f957a
AL
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
308 sensible */
be4401bf
AL
309 if (LastHost != Host)
310 {
311 Owner->Status("Connecting to %s",Host.c_str());
312
313 // Lookup the host
314 hostent *Addr = gethostbyname(Host.c_str());
3000ccea 315 if (Addr == 0 || Addr->h_addr_list[0] == 0)
92e889c8 316 return _error->Error("Could not resolve '%s'",Host.c_str());
be4401bf
AL
317 LastHost = Host;
318 LastHostA = *(in_addr *)(Addr->h_addr_list[0]);
319 }
320
321 Owner->Status("Connecting to %s (%s)",Host.c_str(),inet_ntoa(LastHostA));
322
323 // Get a socket
324 if ((ServerFd = socket(AF_INET,SOCK_STREAM,0)) < 0)
325 return _error->Errno("socket","Could not create a socket");
326
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;
3000ccea
AL
332 SetNonBlock(ServerFd,true);
333 if (connect(ServerFd,(sockaddr *)&server,sizeof(server)) < 0 &&
334 errno != EINPROGRESS)
be4401bf
AL
335 return _error->Errno("socket","Could not create a socket");
336
3000ccea
AL
337 /* This implements a timeout for connect by opening the connection
338 nonblocking */
30b30ec1 339 if (WaitFd(ServerFd,true,TimeOut) == false)
3000ccea 340 return _error->Error("Could not connect, connection timed out");
30b30ec1
AL
341 unsigned int Err;
342 unsigned int Len = sizeof(Err);
3000ccea
AL
343 if (getsockopt(ServerFd,SOL_SOCKET,SO_ERROR,&Err,&Len) != 0)
344 return _error->Errno("getsockopt","Failed");
345 if (Err != 0)
346 return _error->Error("Could not connect.");
347
be4401bf
AL
348 return true;
349}
350 /*}}}*/
351// ServerState::Close - Close a connection to the server /*{{{*/
352// ---------------------------------------------------------------------
353/* */
354bool ServerState::Close()
355{
356 close(ServerFd);
357 ServerFd = -1;
be4401bf
AL
358 return true;
359}
360 /*}}}*/
361// ServerState::RunHeaders - Get the headers before the data /*{{{*/
362// ---------------------------------------------------------------------
92e889c8
AL
363/* Returns 0 if things are OK, 1 if an IO error occursed and 2 if a header
364 parse error occured */
365int ServerState::RunHeaders()
be4401bf
AL
366{
367 State = Header;
368
369 Owner->Status("Waiting for file");
370
371 Major = 0;
372 Minor = 0;
373 Result = 0;
374 Size = 0;
375 StartPos = 0;
92e889c8
AL
376 Encoding = Closes;
377 HaveContent = false;
be4401bf
AL
378 time(&Date);
379
380 do
381 {
382 string Data;
383 if (In.WriteTillEl(Data) == false)
384 continue;
385
386 for (string::const_iterator I = Data.begin(); I < Data.end(); I++)
387 {
388 string::const_iterator J = I;
389 for (; J != Data.end() && *J != '\n' && *J != '\r';J++);
390 if (HeaderLine(string(I,J-I)) == false)
92e889c8 391 return 2;
be4401bf
AL
392 I = J;
393 }
92e889c8 394 return 0;
be4401bf
AL
395 }
396 while (Owner->Go(false,this) == true);
92e889c8
AL
397
398 return 1;
be4401bf
AL
399}
400 /*}}}*/
401// ServerState::RunData - Transfer the data from the socket /*{{{*/
402// ---------------------------------------------------------------------
403/* */
404bool ServerState::RunData()
405{
406 State = Data;
407
408 // Chunked transfer encoding is fun..
409 if (Encoding == Chunked)
410 {
411 while (1)
412 {
413 // Grab the block size
414 bool Last = true;
415 string Data;
416 In.Limit(-1);
417 do
418 {
419 if (In.WriteTillEl(Data,true) == true)
420 break;
421 }
422 while ((Last = Owner->Go(false,this)) == true);
423
424 if (Last == false)
425 return false;
426
427 // See if we are done
428 unsigned long Len = strtol(Data.c_str(),0,16);
429 if (Len == 0)
430 {
431 In.Limit(-1);
432
433 // We have to remove the entity trailer
434 Last = true;
435 do
436 {
437 if (In.WriteTillEl(Data,true) == true && Data.length() <= 2)
438 break;
439 }
440 while ((Last = Owner->Go(false,this)) == true);
441 if (Last == false)
442 return false;
e1b96638 443 return !_error->PendingError();
be4401bf
AL
444 }
445
446 // Transfer the block
447 In.Limit(Len);
448 while (Owner->Go(true,this) == true)
449 if (In.IsLimit() == true)
450 break;
451
452 // Error
453 if (In.IsLimit() == false)
454 return false;
455
456 // The server sends an extra new line before the next block specifier..
457 In.Limit(-1);
458 Last = true;
459 do
460 {
461 if (In.WriteTillEl(Data,true) == true)
462 break;
463 }
464 while ((Last = Owner->Go(false,this)) == true);
465 if (Last == false)
466 return false;
92e889c8 467 }
be4401bf
AL
468 }
469 else
470 {
471 /* Closes encoding is used when the server did not specify a size, the
472 loss of the connection means we are done */
473 if (Encoding == Closes)
474 In.Limit(-1);
475 else
476 In.Limit(Size - StartPos);
477
478 // Just transfer the whole block.
479 do
480 {
481 if (In.IsLimit() == false)
482 continue;
483
484 In.Limit(-1);
e1b96638 485 return !_error->PendingError();
be4401bf
AL
486 }
487 while (Owner->Go(true,this) == true);
488 }
489
e1b96638 490 return Owner->Flush(this) && !_error->PendingError();
be4401bf
AL
491}
492 /*}}}*/
493// ServerState::HeaderLine - Process a header line /*{{{*/
494// ---------------------------------------------------------------------
495/* */
496bool ServerState::HeaderLine(string Line)
497{
498 if (Line.empty() == true)
499 return true;
30456e14 500
be4401bf
AL
501 // The http server might be trying to do something evil.
502 if (Line.length() >= MAXLEN)
503 return _error->Error("Got a single header line over %u chars",MAXLEN);
504
505 string::size_type Pos = Line.find(' ');
506 if (Pos == string::npos || Pos+1 > Line.length())
c901051d
AL
507 {
508 // Blah, some servers use "connection:closes", evil.
509 Pos = Line.find(':');
510 if (Pos == string::npos || Pos + 2 > Line.length())
511 return _error->Error("Bad header line");
512 Pos++;
513 }
be4401bf 514
c901051d
AL
515 // Parse off any trailing spaces between the : and the next word.
516 string::size_type Pos2 = Pos;
517 while (Pos2 < Line.length() && isspace(Line[Pos2]) != 0)
518 Pos2++;
519
520 string Tag = string(Line,0,Pos);
521 string Val = string(Line,Pos2);
522
92e889c8 523 if (stringcasecmp(Tag.begin(),Tag.begin()+4,"HTTP") == 0)
be4401bf
AL
524 {
525 // Evil servers return no version
526 if (Line[4] == '/')
527 {
528 if (sscanf(Line.c_str(),"HTTP/%u.%u %u %[^\n]",&Major,&Minor,
529 &Result,Code) != 4)
530 return _error->Error("The http server sent an invalid reply header");
531 }
532 else
533 {
534 Major = 0;
535 Minor = 9;
536 if (sscanf(Line.c_str(),"HTTP %u %[^\n]",&Result,Code) != 2)
537 return _error->Error("The http server sent an invalid reply header");
538 }
539
540 return true;
541 }
542
92e889c8 543 if (stringcasecmp(Tag,"Content-Length:") == 0)
be4401bf
AL
544 {
545 if (Encoding == Closes)
546 Encoding = Stream;
92e889c8 547 HaveContent = true;
be4401bf
AL
548
549 // The length is already set from the Content-Range header
550 if (StartPos != 0)
551 return true;
552
553 if (sscanf(Val.c_str(),"%lu",&Size) != 1)
554 return _error->Error("The http server sent an invalid Content-Length header");
555 return true;
556 }
557
92e889c8
AL
558 if (stringcasecmp(Tag,"Content-Type:") == 0)
559 {
560 HaveContent = true;
561 return true;
562 }
563
564 if (stringcasecmp(Tag,"Content-Range:") == 0)
be4401bf 565 {
92e889c8
AL
566 HaveContent = true;
567
be4401bf
AL
568 if (sscanf(Val.c_str(),"bytes %lu-%*u/%lu",&StartPos,&Size) != 2)
569 return _error->Error("The http server sent an invalid Content-Range header");
570 if ((unsigned)StartPos > Size)
571 return _error->Error("This http server has broken range support");
572 return true;
573 }
574
92e889c8 575 if (stringcasecmp(Tag,"Transfer-Encoding:") == 0)
be4401bf 576 {
92e889c8
AL
577 HaveContent = true;
578 if (stringcasecmp(Val,"chunked") == 0)
be4401bf 579 Encoding = Chunked;
92e889c8 580
be4401bf
AL
581 return true;
582 }
583
92e889c8 584 if (stringcasecmp(Tag,"Last-Modified:") == 0)
be4401bf
AL
585 {
586 if (StrToTime(Val,Date) == false)
587 return _error->Error("Unknown date format");
588 return true;
589 }
590
591 return true;
592}
593 /*}}}*/
594
595// HttpMethod::SendReq - Send the HTTP request /*{{{*/
596// ---------------------------------------------------------------------
597/* This places the http request in the outbound buffer */
598void HttpMethod::SendReq(FetchItem *Itm,CircleBuf &Out)
599{
600 URI Uri = Itm->Uri;
c1a22377 601
be4401bf 602 // The HTTP server expects a hostname with a trailing :port
c1a22377 603 char Buf[1000];
be4401bf
AL
604 string ProperHost = Uri.Host;
605 if (Uri.Port != 0)
606 {
607 sprintf(Buf,":%u",Uri.Port);
608 ProperHost += Buf;
609 }
610
c1a22377
AL
611 // Just in case.
612 if (Itm->Uri.length() >= sizeof(Buf))
613 abort();
614
492f957a
AL
615 /* Build the request. We include a keep-alive header only for non-proxy
616 requests. This is to tweak old http/1.0 servers that do support keep-alive
617 but not HTTP/1.1 automatic keep-alive. Doing this with a proxy server
618 will glitch HTTP/1.0 proxies because they do not filter it out and
619 pass it on, HTTP/1.1 says the connection should default to keep alive
620 and we expect the proxy to do this */
be4401bf
AL
621 if (Proxy.empty() == true)
622 sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\nConnection: keep-alive\r\n",
a4edf53b 623 QuoteString(Uri.Path,"~").c_str(),ProperHost.c_str());
be4401bf 624 else
c1a22377
AL
625 {
626 /* Generate a cache control header if necessary. We place a max
627 cache age on index files, optionally set a no-cache directive
628 and a no-store directive for archives. */
be4401bf
AL
629 sprintf(Buf,"GET %s HTTP/1.1\r\nHost: %s\r\n",
630 Itm->Uri.c_str(),ProperHost.c_str());
c1a22377 631 if (_config->FindB("Acquire::http::No-Cache",false) == true)
b53b7926 632 strcat(Buf,"Cache-Control: no-cache\r\nPragma: no-cache\r\n");
c1a22377
AL
633 else
634 {
635 if (Itm->IndexFile == true)
636 sprintf(Buf+strlen(Buf),"Cache-Control: max-age=%u\r\n",
637 _config->FindI("Acquire::http::Max-Age",60*60*24));
638 else
639 {
640 if (_config->FindB("Acquire::http::No-Store",false) == true)
641 strcat(Buf,"Cache-Control: no-store\r\n");
642 }
643 }
644 }
645
be4401bf 646 string Req = Buf;
492f957a 647
be4401bf
AL
648 // Check for a partial file
649 struct stat SBuf;
650 if (stat(Itm->DestFile.c_str(),&SBuf) >= 0 && SBuf.st_size > 0)
651 {
652 // In this case we send an if-range query with a range header
653 sprintf(Buf,"Range: bytes=%li-\r\nIf-Range: %s\r\n",SBuf.st_size - 1,
654 TimeRFC1123(SBuf.st_mtime).c_str());
655 Req += Buf;
656 }
657 else
658 {
659 if (Itm->LastModified != 0)
660 {
661 sprintf(Buf,"If-Modified-Since: %s\r\n",TimeRFC1123(Itm->LastModified).c_str());
662 Req += Buf;
663 }
664 }
665
8d64c395
AL
666 if (Proxy.User.empty() == false || Proxy.Password.empty() == false)
667 Req += string("Proxy-Authorization: Basic ") +
668 Base64Encode(Proxy.User + ":" + Proxy.Password) + "\r\n";
be4401bf
AL
669
670 Req += "User-Agent: Debian APT-HTTP/1.2\r\n\r\n";
39536c62 671// cerr << Req << endl;
c1a22377 672
be4401bf
AL
673 Out.Read(Req);
674}
675 /*}}}*/
676// HttpMethod::Go - Run a single loop /*{{{*/
677// ---------------------------------------------------------------------
678/* This runs the select loop over the server FDs, Output file FDs and
679 stdin. */
680bool HttpMethod::Go(bool ToFile,ServerState *Srv)
681{
682 // Server has closed the connection
683 if (Srv->ServerFd == -1 && Srv->In.WriteSpace() == false)
684 return false;
685
686 fd_set rfds,wfds,efds;
687 FD_ZERO(&rfds);
688 FD_ZERO(&wfds);
689 FD_ZERO(&efds);
690
691 // Add the server
692 if (Srv->Out.WriteSpace() == true && Srv->ServerFd != -1)
693 FD_SET(Srv->ServerFd,&wfds);
694 if (Srv->In.ReadSpace() == true && Srv->ServerFd != -1)
695 FD_SET(Srv->ServerFd,&rfds);
696
697 // Add the file
698 int FileFD = -1;
699 if (File != 0)
700 FileFD = File->Fd();
701
702 if (Srv->In.WriteSpace() == true && ToFile == true && FileFD != -1)
703 FD_SET(FileFD,&wfds);
704
705 // Add stdin
706 FD_SET(STDIN_FILENO,&rfds);
707
708 // Error Set
709 if (FileFD != -1)
710 FD_SET(FileFD,&efds);
711 if (Srv->ServerFd != -1)
712 FD_SET(Srv->ServerFd,&efds);
713
714 // Figure out the max fd
715 int MaxFd = FileFD;
716 if (MaxFd < Srv->ServerFd)
717 MaxFd = Srv->ServerFd;
718
719 // Select
720 struct timeval tv;
3000ccea 721 tv.tv_sec = TimeOut;
be4401bf
AL
722 tv.tv_usec = 0;
723 int Res = 0;
724 if ((Res = select(MaxFd+1,&rfds,&wfds,&efds,&tv)) < 0)
725 return _error->Errno("select","Select failed");
726
727 if (Res == 0)
728 {
729 _error->Error("Connection timed out");
730 return ServerDie(Srv);
731 }
732
733 // Some kind of exception (error) on the sockets, die
734 if ((FileFD != -1 && FD_ISSET(FileFD,&efds)) ||
735 (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&efds)))
736 return _error->Error("Socket Exception");
737
738 // Handle server IO
739 if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&rfds))
740 {
741 errno = 0;
742 if (Srv->In.Read(Srv->ServerFd) == false)
743 return ServerDie(Srv);
744 }
745
746 if (Srv->ServerFd != -1 && FD_ISSET(Srv->ServerFd,&wfds))
747 {
748 errno = 0;
749 if (Srv->Out.Write(Srv->ServerFd) == false)
750 return ServerDie(Srv);
751 }
752
753 // Send data to the file
754 if (FileFD != -1 && FD_ISSET(FileFD,&wfds))
755 {
756 if (Srv->In.Write(FileFD) == false)
757 return _error->Errno("write","Error writing to output file");
758 }
759
760 // Handle commands from APT
761 if (FD_ISSET(STDIN_FILENO,&rfds))
762 {
763 if (Run(true) != 0)
764 exit(100);
765 }
766
767 return true;
768}
769 /*}}}*/
770// HttpMethod::Flush - Dump the buffer into the file /*{{{*/
771// ---------------------------------------------------------------------
772/* This takes the current input buffer from the Server FD and writes it
773 into the file */
774bool HttpMethod::Flush(ServerState *Srv)
775{
776 if (File != 0)
777 {
778 SetNonBlock(File->Fd(),false);
779 if (Srv->In.WriteSpace() == false)
780 return true;
781
782 while (Srv->In.WriteSpace() == true)
783 {
784 if (Srv->In.Write(File->Fd()) == false)
785 return _error->Errno("write","Error writing to file");
92e889c8
AL
786 if (Srv->In.IsLimit() == true)
787 return true;
be4401bf
AL
788 }
789
790 if (Srv->In.IsLimit() == true || Srv->Encoding == ServerState::Closes)
791 return true;
792 }
793 return false;
794}
795 /*}}}*/
796// HttpMethod::ServerDie - The server has closed the connection. /*{{{*/
797// ---------------------------------------------------------------------
798/* */
799bool HttpMethod::ServerDie(ServerState *Srv)
800{
2b154e53
AL
801 unsigned int LErrno = errno;
802
be4401bf
AL
803 // Dump the buffer to the file
804 if (Srv->State == ServerState::Data)
805 {
806 SetNonBlock(File->Fd(),false);
807 while (Srv->In.WriteSpace() == true)
808 {
809 if (Srv->In.Write(File->Fd()) == false)
810 return _error->Errno("write","Error writing to the file");
92e889c8
AL
811
812 // Done
813 if (Srv->In.IsLimit() == true)
814 return true;
be4401bf
AL
815 }
816 }
817
818 // See if this is because the server finished the data stream
819 if (Srv->In.IsLimit() == false && Srv->State != ServerState::Header &&
820 Srv->Encoding != ServerState::Closes)
821 {
3d615484 822 Srv->Close();
2b154e53 823 if (LErrno == 0)
be4401bf 824 return _error->Error("Error reading from server Remote end closed connection");
2b154e53 825 errno = LErrno;
be4401bf
AL
826 return _error->Errno("read","Error reading from server");
827 }
828 else
829 {
830 Srv->In.Limit(-1);
831
832 // Nothing left in the buffer
833 if (Srv->In.WriteSpace() == false)
834 return false;
835
836 // We may have got multiple responses back in one packet..
837 Srv->Close();
838 return true;
839 }
840
841 return false;
842}
843 /*}}}*/
844// HttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
845// ---------------------------------------------------------------------
846/* We look at the header data we got back from the server and decide what
847 to do. Returns
848 0 - File is open,
849 1 - IMS hit
92e889c8 850 3 - Unrecoverable error
94235cfb
AL
851 4 - Error with error content page
852 5 - Unrecoverable non-server error (close the connection) */
be4401bf
AL
853int HttpMethod::DealWithHeaders(FetchResult &Res,ServerState *Srv)
854{
855 // Not Modified
856 if (Srv->Result == 304)
857 {
858 unlink(Queue->DestFile.c_str());
859 Res.IMSHit = true;
860 Res.LastModified = Queue->LastModified;
861 return 1;
862 }
863
864 /* We have a reply we dont handle. This should indicate a perm server
865 failure */
866 if (Srv->Result < 200 || Srv->Result >= 300)
867 {
868 _error->Error("%u %s",Srv->Result,Srv->Code);
92e889c8
AL
869 if (Srv->HaveContent == true)
870 return 4;
be4401bf
AL
871 return 3;
872 }
873
874 // This is some sort of 2xx 'data follows' reply
875 Res.LastModified = Srv->Date;
876 Res.Size = Srv->Size;
877
878 // Open the file
879 delete File;
880 File = new FileFd(Queue->DestFile,FileFd::WriteAny);
881 if (_error->PendingError() == true)
94235cfb 882 return 5;
492f957a
AL
883
884 FailFile = Queue->DestFile;
30b30ec1 885 FailFile.c_str(); // Make sure we dont do a malloc in the signal handler
492f957a
AL
886 FailFd = File->Fd();
887 FailTime = Srv->Date;
888
be4401bf
AL
889 // Set the expected size
890 if (Srv->StartPos >= 0)
891 {
892 Res.ResumePoint = Srv->StartPos;
893 ftruncate(File->Fd(),Srv->StartPos);
894 }
895
896 // Set the start point
897 lseek(File->Fd(),0,SEEK_END);
898
899 delete Srv->In.MD5;
900 Srv->In.MD5 = new MD5Summation;
901
902 // Fill the MD5 Hash if the file is non-empty (resume)
903 if (Srv->StartPos > 0)
904 {
905 lseek(File->Fd(),0,SEEK_SET);
906 if (Srv->In.MD5->AddFD(File->Fd(),Srv->StartPos) == false)
907 {
908 _error->Errno("read","Problem hashing file");
94235cfb 909 return 5;
be4401bf
AL
910 }
911 lseek(File->Fd(),0,SEEK_END);
912 }
913
914 SetNonBlock(File->Fd(),true);
915 return 0;
916}
917 /*}}}*/
492f957a
AL
918// HttpMethod::SigTerm - Handle a fatal signal /*{{{*/
919// ---------------------------------------------------------------------
920/* This closes and timestamps the open file. This is neccessary to get
921 resume behavoir on user abort */
922void HttpMethod::SigTerm(int)
923{
924 if (FailFd == -1)
925 exit(100);
926 close(FailFd);
927
928 // Timestamp
929 struct utimbuf UBuf;
492f957a
AL
930 UBuf.actime = FailTime;
931 UBuf.modtime = FailTime;
932 utime(FailFile.c_str(),&UBuf);
933
934 exit(100);
935}
936 /*}}}*/
5cb5d8dc
AL
937// HttpMethod::Fetch - Fetch an item /*{{{*/
938// ---------------------------------------------------------------------
939/* This adds an item to the pipeline. We keep the pipeline at a fixed
940 depth. */
941bool HttpMethod::Fetch(FetchItem *)
942{
943 if (Server == 0)
944 return true;
3000ccea 945
5cb5d8dc
AL
946 // Queue the requests
947 int Depth = -1;
948 bool Tail = false;
c1a22377 949 for (FetchItem *I = Queue; I != 0 && Depth < (signed)PipelineDepth; I = I->Next, Depth++)
5cb5d8dc
AL
950 {
951 // Make sure we stick with the same server
952 if (Server->Comp(I->Uri) == false)
953 break;
5cb5d8dc
AL
954 if (QueueBack == I)
955 Tail = true;
956 if (Tail == true)
957 {
5cb5d8dc
AL
958 QueueBack = I->Next;
959 SendReq(I,Server->Out);
960 continue;
961 }
962 }
963
964 return true;
965};
966 /*}}}*/
85f72a56
AL
967// HttpMethod::Configuration - Handle a configuration message /*{{{*/
968// ---------------------------------------------------------------------
969/* We stash the desired pipeline depth */
970bool HttpMethod::Configuration(string Message)
971{
972 if (pkgAcqMethod::Configuration(Message) == false)
973 return false;
974
30456e14
AL
975 TimeOut = _config->FindI("Acquire::http::Timeout",TimeOut);
976 PipelineDepth = _config->FindI("Acquire::http::Pipeline-Depth",
977 PipelineDepth);
3000ccea 978
85f72a56
AL
979 return true;
980}
981 /*}}}*/
492f957a 982// HttpMethod::Loop - Main loop /*{{{*/
be4401bf
AL
983// ---------------------------------------------------------------------
984/* */
985int HttpMethod::Loop()
986{
492f957a
AL
987 signal(SIGTERM,SigTerm);
988 signal(SIGINT,SigTerm);
989
5cb5d8dc 990 Server = 0;
be4401bf 991
92e889c8 992 int FailCounter = 0;
be4401bf 993 while (1)
2b154e53 994 {
be4401bf
AL
995 // We have no commands, wait for some to arrive
996 if (Queue == 0)
997 {
998 if (WaitFd(STDIN_FILENO) == false)
999 return 0;
1000 }
1001
1002 // Run messages
1003 if (Run(true) != 0)
1004 return 100;
1005
1006 if (Queue == 0)
1007 continue;
1008
1009 // Connect to the server
1010 if (Server == 0 || Server->Comp(Queue->Uri) == false)
1011 {
1012 delete Server;
1013 Server = new ServerState(Queue->Uri,this);
1014 }
1015
a7fb252c
AL
1016 // Reset the pipeline
1017 if (Server->ServerFd == -1)
1018 QueueBack = Queue;
1019
be4401bf
AL
1020 // Connnect to the host
1021 if (Server->Open() == false)
1022 {
43252d15 1023 Fail(true);
a1459f52
AL
1024 delete Server;
1025 Server = 0;
be4401bf
AL
1026 continue;
1027 }
be4401bf 1028
5cb5d8dc
AL
1029 // Fill the pipeline.
1030 Fetch(0);
1031
92e889c8
AL
1032 // Fetch the next URL header data from the server.
1033 switch (Server->RunHeaders())
be4401bf 1034 {
92e889c8
AL
1035 case 0:
1036 break;
1037
1038 // The header data is bad
1039 case 2:
1040 {
1041 _error->Error("Bad header Data");
43252d15 1042 Fail(true);
92e889c8
AL
1043 continue;
1044 }
1045
1046 // The server closed a connection during the header get..
1047 default:
1048 case 1:
1049 {
1050 FailCounter++;
3d615484 1051 _error->Discard();
92e889c8 1052 Server->Close();
2b154e53
AL
1053
1054 if (FailCounter >= 2)
1055 {
1056 Fail("Connection timed out",true);
1057 FailCounter = 0;
1058 }
1059
92e889c8
AL
1060 continue;
1061 }
1062 };
5cb5d8dc 1063
be4401bf
AL
1064 // Decide what to do.
1065 FetchResult Res;
bfd22fc0 1066 Res.Filename = Queue->DestFile;
be4401bf
AL
1067 switch (DealWithHeaders(Res,Server))
1068 {
1069 // Ok, the file is Open
1070 case 0:
1071 {
1072 URIStart(Res);
1073
1074 // Run the data
492f957a
AL
1075 bool Result = Server->RunData();
1076
1077 // Close the file, destroy the FD object and timestamp it
1078 FailFd = -1;
1079 delete File;
1080 File = 0;
1081
1082 // Timestamp
1083 struct utimbuf UBuf;
1084 time(&UBuf.actime);
1085 UBuf.actime = Server->Date;
1086 UBuf.modtime = Server->Date;
1087 utime(Queue->DestFile.c_str(),&UBuf);
1088
1089 // Send status to APT
1090 if (Result == true)
92e889c8
AL
1091 {
1092 Res.MD5Sum = Server->In.MD5->Result();
1093 URIDone(Res);
1094 }
492f957a 1095 else
2b154e53 1096 Fail(true);
492f957a 1097
be4401bf
AL
1098 break;
1099 }
1100
1101 // IMS hit
1102 case 1:
1103 {
1104 URIDone(Res);
1105 break;
1106 }
1107
1108 // Hard server error, not found or something
1109 case 3:
1110 {
1111 Fail();
1112 break;
1113 }
94235cfb
AL
1114
1115 // Hard internal error, kill the connection and fail
1116 case 5:
1117 {
1118 Fail();
1119 Server->Close();
1120 break;
1121 }
92e889c8
AL
1122
1123 // We need to flush the data, the header is like a 404 w/ error text
1124 case 4:
1125 {
1126 Fail();
1127
1128 // Send to content to dev/null
1129 File = new FileFd("/dev/null",FileFd::WriteExists);
1130 Server->RunData();
1131 delete File;
1132 File = 0;
1133 break;
1134 }
be4401bf
AL
1135
1136 default:
1137 Fail("Internal error");
1138 break;
92e889c8
AL
1139 }
1140
1141 FailCounter = 0;
be4401bf
AL
1142 }
1143
1144 return 0;
1145}
1146 /*}}}*/
1147
1148int main()
1149{
1150 HttpMethod Mth;
1151
1152 return Mth.Loop();
1153}