]>
git.saurik.com Git - apt.git/blob - methods/basehttp.cc
1 // -*- mode: cpp; mode: fold -*-
3 /* ######################################################################
5 HTTP and HTTPS share a lot of common code and these classes are
6 exactly the dumping ground for this common code
8 ##################################################################### */
10 // Include Files /*{{{*/
13 #include <apt-pkg/configuration.h>
14 #include <apt-pkg/error.h>
15 #include <apt-pkg/fileutl.h>
16 #include <apt-pkg/strutl.h>
38 string
BaseHttpMethod::FailFile
;
39 int BaseHttpMethod::FailFd
= -1;
40 time_t BaseHttpMethod::FailTime
= 0;
42 // ServerState::RunHeaders - Get the headers before the data /*{{{*/
43 // ---------------------------------------------------------------------
44 /* Returns 0 if things are OK, 1 if an IO error occurred and 2 if a header
45 parse error occurred */
46 ServerState::RunHeadersResult
ServerState::RunHeaders(RequestState
&Req
,
47 const std::string
&Uri
)
49 Owner
->Status(_("Waiting for headers"));
53 if (ReadHeaderLines(Data
) == false)
56 if (Owner
->Debug
== true)
57 clog
<< "Answer for: " << Uri
<< endl
<< Data
;
59 for (string::const_iterator I
= Data
.begin(); I
< Data
.end(); ++I
)
61 string::const_iterator J
= I
;
62 for (; J
!= Data
.end() && *J
!= '\n' && *J
!= '\r'; ++J
);
63 if (Req
.HeaderLine(string(I
,J
)) == false)
64 return RUN_HEADERS_PARSE_ERROR
;
68 // 100 Continue is a Nop...
69 if (Req
.Result
== 100)
72 // Tidy up the connection persistence state.
73 if (Req
.Encoding
== RequestState::Closes
&& Req
.HaveContent
== true)
76 return RUN_HEADERS_OK
;
78 while (LoadNextResponse(false, Req
) == true);
80 return RUN_HEADERS_IO_ERROR
;
83 bool RequestState::HeaderLine(string
const &Line
) /*{{{*/
85 if (Line
.empty() == true)
88 if (Line
.size() > 4 && stringcasecmp(Line
.data(), Line
.data()+4, "HTTP") == 0)
90 // Evil servers return no version
93 int const elements
= sscanf(Line
.c_str(),"HTTP/%3u.%3u %3u%359[^\n]",&Major
,&Minor
,&Result
,Code
);
97 if (Owner
!= NULL
&& Owner
->Debug
== true)
98 clog
<< "HTTP server doesn't give Reason-Phrase for " << std::to_string(Result
) << std::endl
;
100 else if (elements
!= 4)
101 return _error
->Error(_("The HTTP server sent an invalid reply header"));
107 if (sscanf(Line
.c_str(),"HTTP %3u%359[^\n]",&Result
,Code
) != 2)
108 return _error
->Error(_("The HTTP server sent an invalid reply header"));
111 /* Check the HTTP response header to get the default persistence
114 Server
->Persistent
= false;
117 if (Major
== 1 && Minor
== 0)
119 Server
->Persistent
= false;
123 Server
->Persistent
= true;
124 if (Server
->PipelineAllowed
)
125 Server
->Pipeline
= true;
132 // Blah, some servers use "connection:closes", evil.
133 // and some even send empty header fields…
134 string::size_type Pos
= Line
.find(':');
135 if (Pos
== string::npos
)
136 return _error
->Error(_("Bad header line"));
139 // Parse off any trailing spaces between the : and the next word.
140 string::size_type Pos2
= Pos
;
141 while (Pos2
< Line
.length() && isspace_ascii(Line
[Pos2
]) != 0)
144 string
const Tag(Line
,0,Pos
);
145 string
const Val(Line
,Pos2
);
147 if (stringcasecmp(Tag
,"Content-Length:") == 0)
149 if (Encoding
== Closes
)
153 unsigned long long * DownloadSizePtr
= &DownloadSize
;
154 if (Result
== 416 || (Result
>= 300 && Result
< 400))
155 DownloadSizePtr
= &JunkSize
;
157 *DownloadSizePtr
= strtoull(Val
.c_str(), NULL
, 10);
158 if (*DownloadSizePtr
>= std::numeric_limits
<unsigned long long>::max())
159 return _error
->Errno("HeaderLine", _("The HTTP server sent an invalid Content-Length header"));
160 else if (*DownloadSizePtr
== 0)
163 // On partial content (206) the Content-Length less than the real
164 // size, so do not set it here but leave that to the Content-Range
166 if(Result
!= 206 && TotalFileSize
== 0)
167 TotalFileSize
= DownloadSize
;
172 if (stringcasecmp(Tag
,"Content-Type:") == 0)
178 if (stringcasecmp(Tag
,"Content-Range:") == 0)
182 // §14.16 says 'byte-range-resp-spec' should be a '*' in case of 416
183 if (Result
== 416 && sscanf(Val
.c_str(), "bytes */%llu",&TotalFileSize
) == 1)
184 ; // we got the expected filesize which is all we wanted
185 else if (sscanf(Val
.c_str(),"bytes %llu-%*u/%llu",&StartPos
,&TotalFileSize
) != 2)
186 return _error
->Error(_("The HTTP server sent an invalid Content-Range header"));
187 if ((unsigned long long)StartPos
> TotalFileSize
)
188 return _error
->Error(_("This HTTP server has broken range support"));
190 // figure out what we will download
191 DownloadSize
= TotalFileSize
- StartPos
;
195 if (stringcasecmp(Tag
,"Transfer-Encoding:") == 0)
198 if (stringcasecmp(Val
,"chunked") == 0)
203 if (stringcasecmp(Tag
,"Connection:") == 0)
205 if (stringcasecmp(Val
,"close") == 0)
207 Server
->Persistent
= false;
208 Server
->Pipeline
= false;
209 /* Some servers send error pages (as they are dynamically generated)
210 for simplicity via a connection close instead of e.g. chunked,
211 so assuming an always closing server only if we get a file + close */
212 if (Result
>= 200 && Result
< 300)
213 Server
->PipelineAllowed
= false;
215 else if (stringcasecmp(Val
,"keep-alive") == 0)
216 Server
->Persistent
= true;
220 if (stringcasecmp(Tag
,"Last-Modified:") == 0)
222 if (RFC1123StrToTime(Val
.c_str(), Date
) == false)
223 return _error
->Error(_("Unknown date format"));
227 if (stringcasecmp(Tag
,"Location:") == 0)
233 if (stringcasecmp(Tag
, "Accept-Ranges:") == 0)
235 std::string ranges
= ',' + Val
+ ',';
236 ranges
.erase(std::remove(ranges
.begin(), ranges
.end(), ' '), ranges
.end());
237 if (ranges
.find(",bytes,") == std::string::npos
)
238 Server
->RangesAllowed
= false;
245 // ServerState::ServerState - Constructor /*{{{*/
246 ServerState::ServerState(URI Srv
, BaseHttpMethod
*Owner
) :
247 ServerName(Srv
), TimeOut(120), Owner(Owner
)
252 bool RequestState::AddPartialFileToHashes(FileFd
&File
) /*{{{*/
254 File
.Truncate(StartPos
);
255 return Server
->GetHashes()->AddFD(File
, StartPos
);
258 void ServerState::Reset() /*{{{*/
262 PipelineAllowed
= true;
263 RangesAllowed
= true;
267 // BaseHttpMethod::DealWithHeaders - Handle the retrieved header data /*{{{*/
268 // ---------------------------------------------------------------------
269 /* We look at the header data we got back from the server and decide what
270 to do. Returns DealWithHeadersResult (see http.h for details).
272 BaseHttpMethod::DealWithHeadersResult
273 BaseHttpMethod::DealWithHeaders(FetchResult
&Res
, RequestState
&Req
)
276 if (Req
.Result
== 304)
278 RemoveFile("server", Queue
->DestFile
);
280 Res
.LastModified
= Queue
->LastModified
;
287 * Note that it is only OK for us to treat all redirection the same
288 * because we *always* use GET, not other HTTP methods. There are
289 * three redirection codes for which it is not appropriate that we
290 * redirect. Pass on those codes so the error handling kicks in.
293 && (Req
.Result
> 300 && Req
.Result
< 400)
294 && (Req
.Result
!= 300 // Multiple Choices
295 && Req
.Result
!= 304 // Not Modified
296 && Req
.Result
!= 306)) // (Not part of HTTP/1.1, reserved)
298 if (Req
.Location
.empty() == true)
300 else if (Req
.Location
[0] == '/' && Queue
->Uri
.empty() == false)
302 URI Uri
= Queue
->Uri
;
303 if (Uri
.Host
.empty() == false)
304 NextURI
= URI::SiteOnly(Uri
);
307 NextURI
.append(DeQuoteString(Req
.Location
));
308 if (Queue
->Uri
== NextURI
)
310 SetFailReason("RedirectionLoop");
311 _error
->Error("Redirection loop encountered");
312 if (Req
.HaveContent
== true)
313 return ERROR_WITH_CONTENT_PAGE
;
314 return ERROR_UNRECOVERABLE
;
316 return TRY_AGAIN_OR_REDIRECT
;
320 NextURI
= DeQuoteString(Req
.Location
);
321 URI tmpURI
= NextURI
;
322 if (tmpURI
.Access
.find('+') != std::string::npos
)
324 _error
->Error("Server tried to trick us into using a specific implementation: %s", tmpURI
.Access
.c_str());
325 if (Req
.HaveContent
== true)
326 return ERROR_WITH_CONTENT_PAGE
;
327 return ERROR_UNRECOVERABLE
;
329 URI Uri
= Queue
->Uri
;
330 if (Binary
.find('+') != std::string::npos
)
332 auto base
= Binary
.substr(0, Binary
.find('+'));
333 if (base
!= tmpURI
.Access
)
335 tmpURI
.Access
= base
+ '+' + tmpURI
.Access
;
336 if (tmpURI
.Access
== Binary
)
338 std::string tmpAccess
= Uri
.Access
;
339 std::swap(tmpURI
.Access
, Uri
.Access
);
341 std::swap(tmpURI
.Access
, Uri
.Access
);
347 if (Queue
->Uri
== NextURI
)
349 SetFailReason("RedirectionLoop");
350 _error
->Error("Redirection loop encountered");
351 if (Req
.HaveContent
== true)
352 return ERROR_WITH_CONTENT_PAGE
;
353 return ERROR_UNRECOVERABLE
;
356 // same protocol redirects are okay
357 if (tmpURI
.Access
== Uri
.Access
)
358 return TRY_AGAIN_OR_REDIRECT
;
359 // as well as http to https
360 else if ((Uri
.Access
== "http" || Uri
.Access
== "https+http") && tmpURI
.Access
== "https")
361 return TRY_AGAIN_OR_REDIRECT
;
364 auto const tmpplus
= tmpURI
.Access
.find('+');
365 if (tmpplus
!= std::string::npos
&& tmpURI
.Access
.substr(tmpplus
+ 1) == "https")
367 auto const uriplus
= Uri
.Access
.find('+');
368 if (uriplus
== std::string::npos
)
370 if (Uri
.Access
== tmpURI
.Access
.substr(0, tmpplus
)) // foo -> foo+https
371 return TRY_AGAIN_OR_REDIRECT
;
373 else if (Uri
.Access
.substr(uriplus
+ 1) == "http" &&
374 Uri
.Access
.substr(0, uriplus
) == tmpURI
.Access
.substr(0, tmpplus
)) // foo+http -> foo+https
375 return TRY_AGAIN_OR_REDIRECT
;
378 _error
->Error("Redirection from %s to '%s' is forbidden", Uri
.Access
.c_str(), NextURI
.c_str());
380 /* else pass through for error message */
382 // retry after an invalid range response without partial data
383 else if (Req
.Result
== 416)
386 if (stat(Queue
->DestFile
.c_str(),&SBuf
) >= 0 && SBuf
.st_size
> 0)
388 bool partialHit
= false;
389 if (Queue
->ExpectedHashes
.usable() == true)
391 Hashes
resultHashes(Queue
->ExpectedHashes
);
392 FileFd
file(Queue
->DestFile
, FileFd::ReadOnly
);
393 Req
.TotalFileSize
= file
.FileSize();
394 Req
.Date
= file
.ModificationTime();
395 resultHashes
.AddFD(file
);
396 HashStringList
const hashList
= resultHashes
.GetHashStringList();
397 partialHit
= (Queue
->ExpectedHashes
== hashList
);
399 else if ((unsigned long long)SBuf
.st_size
== Req
.TotalFileSize
)
401 if (partialHit
== true)
403 // the file is completely downloaded, but was not moved
404 if (Req
.HaveContent
== true)
406 // nuke the sent error page
407 Server
->RunDataToDevNull(Req
);
408 Req
.HaveContent
= false;
410 Req
.StartPos
= Req
.TotalFileSize
;
413 else if (RemoveFile("server", Queue
->DestFile
))
415 NextURI
= Queue
->Uri
;
416 return TRY_AGAIN_OR_REDIRECT
;
421 /* We have a reply we don't handle. This should indicate a perm server
423 if (Req
.Result
< 200 || Req
.Result
>= 300)
425 if (_error
->PendingError() == false)
428 strprintf(err
, "HttpError%u", Req
.Result
);
430 _error
->Error("%u %s", Req
.Result
, Req
.Code
);
432 if (Req
.HaveContent
== true)
433 return ERROR_WITH_CONTENT_PAGE
;
434 return ERROR_UNRECOVERABLE
;
437 // This is some sort of 2xx 'data follows' reply
438 Res
.LastModified
= Req
.Date
;
439 Res
.Size
= Req
.TotalFileSize
;
443 // BaseHttpMethod::SigTerm - Handle a fatal signal /*{{{*/
444 // ---------------------------------------------------------------------
445 /* This closes and timestamps the open file. This is necessary to get
446 resume behavoir on user abort */
447 void BaseHttpMethod::SigTerm(int)
452 struct timeval times
[2];
453 times
[0].tv_sec
= FailTime
;
454 times
[1].tv_sec
= FailTime
;
455 times
[0].tv_usec
= times
[1].tv_usec
= 0;
456 utimes(FailFile
.c_str(), times
);
462 // BaseHttpMethod::Fetch - Fetch an item /*{{{*/
463 // ---------------------------------------------------------------------
464 /* This adds an item to the pipeline. We keep the pipeline at a fixed
466 bool BaseHttpMethod::Fetch(FetchItem
*)
468 if (Server
== nullptr || QueueBack
== nullptr)
471 // If pipelining is disabled, we only queue 1 request
472 auto const AllowedDepth
= Server
->Pipeline
? PipelineDepth
: 0;
473 // how deep is our pipeline currently?
474 decltype(PipelineDepth
) CurrentDepth
= 0;
475 for (FetchItem
const *I
= Queue
; I
!= QueueBack
; I
= I
->Next
)
477 if (CurrentDepth
> AllowedDepth
)
481 // Make sure we stick with the same server
482 if (Server
->Comp(QueueBack
->Uri
) == false)
485 bool const UsableHashes
= QueueBack
->ExpectedHashes
.usable();
486 // if we have no hashes, do at most one such request
487 // as we can't fixup pipeling misbehaviors otherwise
488 if (CurrentDepth
!= 0 && UsableHashes
== false)
491 if (UsableHashes
&& FileExists(QueueBack
->DestFile
))
493 FileFd
partial(QueueBack
->DestFile
, FileFd::ReadOnly
);
494 Hashes
wehave(QueueBack
->ExpectedHashes
);
495 if (QueueBack
->ExpectedHashes
.FileSize() == partial
.FileSize())
497 if (wehave
.AddFD(partial
) &&
498 wehave
.GetHashStringList() == QueueBack
->ExpectedHashes
)
501 Res
.Filename
= QueueBack
->DestFile
;
502 Res
.ResumePoint
= QueueBack
->ExpectedHashes
.FileSize();
504 // move item to the start of the queue as URIDone will
505 // always dequeued the first item in the queue
506 if (Queue
!= QueueBack
)
508 FetchItem
*Prev
= Queue
;
509 for (; Prev
->Next
!= QueueBack
; Prev
= Prev
->Next
)
510 /* look for the previous queue item */;
511 Prev
->Next
= QueueBack
->Next
;
512 QueueBack
->Next
= Queue
;
514 QueueBack
= Prev
->Next
;
516 Res
.TakeHashes(wehave
);
521 RemoveFile("Fetch-Partial", QueueBack
->DestFile
);
524 auto const Tmp
= QueueBack
;
525 QueueBack
= QueueBack
->Next
;
528 } while (CurrentDepth
<= AllowedDepth
&& QueueBack
!= nullptr);
533 // BaseHttpMethod::Loop - Main loop /*{{{*/
534 int BaseHttpMethod::Loop()
536 signal(SIGTERM
,SigTerm
);
537 signal(SIGINT
,SigTerm
);
544 // We have no commands, wait for some to arrive
547 if (WaitFd(STDIN_FILENO
) == false)
551 /* Run messages, we can accept 0 (no message) if we didn't
552 do a WaitFd above.. Otherwise the FD is closed. */
553 int Result
= Run(true);
554 if (Result
!= -1 && (Result
!= 0 || Queue
== 0))
556 if(FailReason
.empty() == false ||
557 ConfigFindB("DependOnSTDIN", true) == true)
566 // Connect to the server
567 if (Server
== 0 || Server
->Comp(Queue
->Uri
) == false)
569 Server
= CreateServerState(Queue
->Uri
);
570 setPostfixForMethodNames(::URI(Queue
->Uri
).Host
.c_str());
571 AllowRedirect
= ConfigFindB("AllowRedirect", true);
572 PipelineDepth
= ConfigFindI("Pipeline-Depth", 10);
573 Debug
= DebugEnabled();
576 /* If the server has explicitly said this is the last connection
577 then we pre-emptively shut down the pipeline and tear down
578 the connection. This will speed up HTTP/1.0 servers a tad
579 since we don't have to wait for the close sequence to
581 if (Server
->Persistent
== false)
584 // Reset the pipeline
585 if (Server
->IsOpen() == false)
588 // Connnect to the host
589 if (Server
->Open() == false)
596 // Fill the pipeline.
599 RequestState
Req(this, Server
.get());
600 // Fetch the next URL header data from the server.
601 switch (Server
->RunHeaders(Req
, Queue
->Uri
))
603 case ServerState::RUN_HEADERS_OK
:
606 // The header data is bad
607 case ServerState::RUN_HEADERS_PARSE_ERROR
:
609 _error
->Error(_("Bad header data"));
616 // The server closed a connection during the header get..
618 case ServerState::RUN_HEADERS_IO_ERROR
:
623 Server
->Pipeline
= false;
624 Server
->PipelineAllowed
= false;
626 if (FailCounter
>= 2)
628 Fail(_("Connection failed"),true);
637 // Decide what to do.
639 Res
.Filename
= Queue
->DestFile
;
640 switch (DealWithHeaders(Res
, Req
))
642 // Ok, the file is Open
650 // ensure we don't fetch too much
651 // we could do "Server->MaximumSize = Queue->MaximumSize" here
652 // but that would break the clever pipeline messup detection
653 // so instead we use the size of the biggest item in the queue
654 Req
.MaximumSize
= FindMaximumObjectSizeInQueue();
657 Result
= Server
->RunData(Req
);
659 /* If the server is sending back sizeless responses then fill in
662 Res
.Size
= Req
.File
.Size();
664 // Close the file, destroy the FD object and timestamp it
669 struct timeval times
[2];
670 times
[0].tv_sec
= times
[1].tv_sec
= Req
.Date
;
671 times
[0].tv_usec
= times
[1].tv_usec
= 0;
672 utimes(Queue
->DestFile
.c_str(), times
);
674 // Send status to APT
677 Hashes
* const resultHashes
= Server
->GetHashes();
678 HashStringList
const hashList
= resultHashes
->GetHashStringList();
679 if (PipelineDepth
!= 0 && Queue
->ExpectedHashes
.usable() == true && Queue
->ExpectedHashes
!= hashList
)
681 // we did not get the expected hash… mhhh:
682 // could it be that server/proxy messed up pipelining?
683 FetchItem
* BeforeI
= Queue
;
684 for (FetchItem
*I
= Queue
->Next
; I
!= 0 && I
!= QueueBack
; I
= I
->Next
)
686 if (I
->ExpectedHashes
.usable() == true && I
->ExpectedHashes
== hashList
)
688 // yes, he did! Disable pipelining and rewrite queue
689 if (Server
->Pipeline
== true)
691 Warning(_("Automatically disabled %s due to incorrect response from server/proxy. (man 5 apt.conf)"), "Acquire::http::Pipeline-Depth");
692 Server
->Pipeline
= false;
693 Server
->PipelineAllowed
= false;
694 // we keep the PipelineDepth value so that the rest of the queue can be fixed up as well
696 Rename(Res
.Filename
, I
->DestFile
);
697 Res
.Filename
= I
->DestFile
;
698 BeforeI
->Next
= I
->Next
;
706 Res
.TakeHashes(*resultHashes
);
711 if (Server
->IsOpen() == false)
717 if (FailCounter
>= 2)
719 Fail(_("Connection failed"),true);
741 // Hard server error, not found or something
742 case ERROR_UNRECOVERABLE
:
748 // Hard internal error, kill the connection and fail
749 case ERROR_NOT_FROM_SERVER
:
757 // We need to flush the data, the header is like a 404 w/ error text
758 case ERROR_WITH_CONTENT_PAGE
:
760 Server
->RunDataToDevNull(Req
);
765 // Try again with a new URL
766 case TRY_AGAIN_OR_REDIRECT
:
768 // Clear rest of response if there is content
770 Server
->RunDataToDevNull(Req
);
776 Fail(_("Internal error"));
786 unsigned long long BaseHttpMethod::FindMaximumObjectSizeInQueue() const /*{{{*/
788 unsigned long long MaxSizeInQueue
= 0;
789 for (FetchItem
*I
= Queue
; I
!= 0 && I
!= QueueBack
; I
= I
->Next
)
790 MaxSizeInQueue
= std::max(MaxSizeInQueue
, I
->MaximumSize
);
791 return MaxSizeInQueue
;
794 BaseHttpMethod::BaseHttpMethod(std::string
&&Binary
, char const * const Ver
,unsigned long const Flags
) :/*{{{*/
795 aptMethod(std::move(Binary
), Ver
, Flags
), Server(nullptr), PipelineDepth(10),
796 AllowRedirect(false), Debug(false)
800 bool BaseHttpMethod::Configuration(std::string Message
) /*{{{*/
802 if (aptMethod::Configuration(Message
) == false)
805 _config
->CndSet("Acquire::tor::Proxy",
806 "socks5h://apt-transport-tor@localhost:9050");
810 bool BaseHttpMethod::AddProxyAuth(URI
&Proxy
, URI
const &Server
) const /*{{{*/
812 if (std::find(methodNames
.begin(), methodNames
.end(), "tor") != methodNames
.end() &&
813 Proxy
.User
== "apt-transport-tor" && Proxy
.Password
.empty())
815 std::string pass
= Server
.Host
;
816 pass
.erase(std::remove_if(pass
.begin(), pass
.end(), [](char const c
) { return std::isalnum(c
) == 0; }), pass
.end());
817 if (pass
.length() > 255)
818 Proxy
.Password
= pass
.substr(0, 255);
820 Proxy
.Password
= std::move(pass
);
822 // FIXME: should we support auth.conf for proxies?