]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/HttpRequest.cc
2 * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 73 HTTP Request */
12 #include "AccessLogEntry.h"
13 #include "acl/AclSizeLimit.h"
14 #include "acl/FilledChecklist.h"
15 #include "client_side.h"
16 #include "client_side_request.h"
17 #include "dns/LookupDetails.h"
18 #include "Downloader.h"
19 #include "err_detail_type.h"
23 #include "http/one/RequestParser.h"
24 #include "http/Stream.h"
25 #include "HttpHdrCc.h"
26 #include "HttpHeaderRange.h"
27 #include "HttpRequest.h"
28 #include "log/Config.h"
30 #include "sbuf/StringConvert.h"
31 #include "SquidConfig.h"
36 #include "auth/UserRequest.h"
39 #include "adaptation/icap/icap_log.h"
42 HttpRequest::HttpRequest(const MasterXaction::Pointer
&mx
) :
43 Http::Message(hoRequest
),
50 HttpRequest::HttpRequest(const HttpRequestMethod
& aMethod
, AnyP::ProtocolType aProtocol
, const char *aSchemeImg
, const char *aUrlpath
, const MasterXaction::Pointer
&mx
) :
51 Http::Message(hoRequest
),
55 static unsigned int id
= 1;
56 debugs(93,7, HERE
<< "constructed, this=" << this << " id=" << ++id
);
58 initHTTP(aMethod
, aProtocol
, aSchemeImg
, aUrlpath
);
61 HttpRequest::~HttpRequest()
64 debugs(93,7, HERE
<< "destructed, this=" << this);
68 HttpRequest::initHTTP(const HttpRequestMethod
& aMethod
, AnyP::ProtocolType aProtocol
, const char *aSchemeImg
, const char *aUrlpath
)
71 url
.setScheme(aProtocol
, aSchemeImg
);
78 method
= Http::METHOD_NONE
;
81 auth_user_request
= NULL
;
83 memset(&flags
, '\0', sizeof(flags
));
88 client_addr
.setEmpty();
94 errDetail
= ERR_DETAIL_NONE
;
95 peer_login
= NULL
; // not allocated/deallocated by this class
96 peer_domain
= NULL
; // not allocated/deallocated by this class
98 vary_headers
= SBuf();
99 myportname
= null_string
;
102 extacl_user
= null_string
;
103 extacl_passwd
= null_string
;
105 extacl_log
= null_string
;
106 extacl_message
= null_string
;
107 pstate
= Http::Message::psReadyToParseStartLine
;
108 #if FOLLOW_X_FORWARDED_FOR
109 indirect_client_addr
.setEmpty();
110 #endif /* FOLLOW_X_FORWARDED_FOR */
112 adaptHistory_
= NULL
;
117 rangeOffsetLimit
= -2; //a value of -2 means not checked yet
118 forcedBodyContinuation
= false;
124 // we used to assert that the pipe is NULL, but now the request only
125 // points to a pipe that is owned and initiated by another object.
128 auth_user_request
= NULL
;
130 vary_headers
.clear();
136 delete cache_control
;
137 cache_control
= NULL
;
152 extacl_passwd
.clean();
156 extacl_message
.clean();
161 adaptHistory_
= NULL
;
176 HttpRequest::clone() const
178 HttpRequest
*copy
= new HttpRequest(masterXaction
);
179 copy
->method
= method
;
180 // TODO: move common cloning clone to Msg::copyTo() or copy ctor
181 copy
->header
.append(&header
);
182 copy
->hdrCacheInit();
183 copy
->hdr_sz
= hdr_sz
;
184 copy
->http_ver
= http_ver
;
185 copy
->pstate
= pstate
; // TODO: should we assert a specific state here?
186 copy
->body_pipe
= body_pipe
;
190 // range handled in hdrCacheInit()
192 copy
->imslen
= imslen
;
193 copy
->hier
= hier
; // Is it safe to copy? Should we?
195 copy
->errType
= errType
;
197 // XXX: what to do with copy->peer_login?
199 copy
->lastmod
= lastmod
;
201 copy
->vary_headers
= vary_headers
;
202 // XXX: what to do with copy->peer_domain?
205 copy
->extacl_log
= extacl_log
;
206 copy
->extacl_message
= extacl_message
;
208 const bool inheritWorked
= copy
->inheritProperties(this);
209 assert(inheritWorked
);
215 HttpRequest::inheritProperties(const Http::Message
*aMsg
)
217 const HttpRequest
* aReq
= dynamic_cast<const HttpRequest
*>(aMsg
);
221 client_addr
= aReq
->client_addr
;
222 #if FOLLOW_X_FORWARDED_FOR
223 indirect_client_addr
= aReq
->indirect_client_addr
;
225 my_addr
= aReq
->my_addr
;
227 dnsWait
= aReq
->dnsWait
;
230 adaptHistory_
= aReq
->adaptHistory();
233 icapHistory_
= aReq
->icapHistory();
236 // This may be too conservative for the 204 No Content case
237 // may eventually need cloneNullAdaptationImmune() for that.
238 flags
= aReq
->flags
.cloneAdaptationImmune();
240 errType
= aReq
->errType
;
241 errDetail
= aReq
->errDetail
;
243 auth_user_request
= aReq
->auth_user_request
;
244 extacl_user
= aReq
->extacl_user
;
245 extacl_passwd
= aReq
->extacl_passwd
;
248 myportname
= aReq
->myportname
;
250 forcedBodyContinuation
= aReq
->forcedBodyContinuation
;
252 // main property is which connection the request was received on (if any)
253 clientConnectionManager
= aReq
->clientConnectionManager
;
255 downloader
= aReq
->downloader
;
257 theNotes
= aReq
->theNotes
;
259 sources
= aReq
->sources
;
264 * Checks the first line of an HTTP request is valid
265 * currently just checks the request method is present.
267 * NP: Other errors are left for detection later in the parse.
270 HttpRequest::sanityCheckStartLine(const char *buf
, const size_t hdr_len
, Http::StatusCode
*error
)
272 // content is long enough to possibly hold a reply
273 // 2 being magic size of a 1-byte request method plus space delimiter
275 // this is ony a real error if the headers apparently complete.
277 debugs(58, 3, HERE
<< "Too large request header (" << hdr_len
<< " bytes)");
278 *error
= Http::scInvalidHeader
;
283 /* See if the request buffer starts with a non-whitespace HTTP request 'method'. */
285 m
.HttpRequestMethodXXX(buf
);
286 if (m
== Http::METHOD_NONE
) {
287 debugs(73, 3, "HttpRequest::sanityCheckStartLine: did not find HTTP request method");
288 *error
= Http::scInvalidHeader
;
296 HttpRequest::parseFirstLine(const char *start
, const char *end
)
298 method
.HttpRequestMethodXXX(start
);
300 if (method
== Http::METHOD_NONE
)
303 // XXX: performance regression, strcspn() over the method bytes a second time.
304 // cheaper than allocate+copy+deallocate cycle to SBuf convert a piece of start.
305 const char *t
= start
+ strcspn(start
, w_space
);
307 start
= t
+ strspn(t
, w_space
); // skip w_space after method
309 const char *ver
= findTrailingHTTPVersion(start
, end
);
314 while (xisspace(*end
)) // find prev non-space
317 ++end
; // back to space
319 if (2 != sscanf(ver
+ 5, "%d.%d", &http_ver
.major
, &http_ver
.minor
)) {
320 debugs(73, DBG_IMPORTANT
, "parseRequestLine: Invalid HTTP identifier.");
328 if (end
< start
) // missing URI
333 * (char *) end
= '\0'; // temp terminate URI, XXX dangerous?
335 const bool ret
= url
.parse(method
, start
);
337 * (char *) end
= save
;
342 /* swaps out request using httpRequestPack */
344 HttpRequest::swapOut(StoreEntry
* e
)
352 /* packs request-line and headers, appends <crlf> terminator */
354 HttpRequest::pack(Packable
* p
) const
357 /* pack request-line */
358 p
->appendf(SQUIDSBUFPH
" " SQUIDSBUFPH
" HTTP/%d.%d\r\n",
359 SQUIDSBUFPRINT(method
.image()), SQUIDSBUFPRINT(url
.path()),
360 http_ver
.major
, http_ver
.minor
);
364 p
->append("\r\n", 2);
368 * A wrapper for debugObj()
371 httpRequestPack(void *obj
, Packable
*p
)
373 HttpRequest
*request
= static_cast<HttpRequest
*>(obj
);
377 /* returns the length of request line + headers + crlf */
379 HttpRequest::prefixLen() const
381 return method
.image().length() + 1 +
382 url
.path().length() + 1 +
387 /* sync this routine when you update HttpRequest struct */
389 HttpRequest::hdrCacheInit()
391 Http::Message::hdrCacheInit();
394 range
= header
.getRange();
398 Adaptation::Icap::History::Pointer
399 HttpRequest::icapHistory() const
402 if (Log::TheConfig
.hasIcapToken
|| IcapLogfileStatus
== LOG_ENABLE
) {
403 icapHistory_
= new Adaptation::Icap::History();
404 debugs(93,4, HERE
<< "made " << icapHistory_
<< " for " << this);
413 Adaptation::History::Pointer
414 HttpRequest::adaptHistory(bool createIfNone
) const
416 if (!adaptHistory_
&& createIfNone
) {
417 adaptHistory_
= new Adaptation::History();
418 debugs(93,4, HERE
<< "made " << adaptHistory_
<< " for " << this);
421 return adaptHistory_
;
424 Adaptation::History::Pointer
425 HttpRequest::adaptLogHistory() const
427 return HttpRequest::adaptHistory(Log::TheConfig
.hasAdaptToken
);
431 HttpRequest::adaptHistoryImport(const HttpRequest
&them
)
433 if (!adaptHistory_
) {
434 adaptHistory_
= them
.adaptHistory_
; // may be nil
436 // check that histories did not diverge
437 Must(!them
.adaptHistory_
|| them
.adaptHistory_
== adaptHistory_
);
444 HttpRequest::multipartRangeRequest() const
446 return (range
&& range
->specs
.size() > 1);
450 HttpRequest::bodyNibbled() const
452 return body_pipe
!= NULL
&& body_pipe
->consumedSize() > 0;
456 HttpRequest::detailError(err_type aType
, int aDetail
)
458 if (errType
|| errDetail
)
459 debugs(11, 5, HERE
<< "old error details: " << errType
<< '/' << errDetail
);
460 debugs(11, 5, HERE
<< "current error details: " << aType
<< '/' << aDetail
);
461 // checking type and detail separately may cause inconsistency, but
462 // may result in more details available if they only become available later
470 HttpRequest::clearError()
472 debugs(11, 7, HERE
<< "old error details: " << errType
<< '/' << errDetail
);
474 errDetail
= ERR_DETAIL_NONE
;
478 HttpRequest::packFirstLineInto(Packable
* p
, bool full_uri
) const
480 const SBuf
tmp(full_uri
? effectiveRequestUri() : url
.path());
482 // form HTTP request-line
483 p
->appendf(SQUIDSBUFPH
" " SQUIDSBUFPH
" HTTP/%d.%d\r\n",
484 SQUIDSBUFPRINT(method
.image()),
486 http_ver
.major
, http_ver
.minor
);
490 * Indicate whether or not we would expect an entity-body
491 * along with this request
494 HttpRequest::expectingBody(const HttpRequestMethod
&, int64_t &theSize
) const
496 bool expectBody
= false;
499 * Note: Checks for message validity is in clientIsContentLengthValid().
500 * this just checks if a entity-body is expected based on HTTP message syntax
502 if (header
.chunked()) {
505 } else if (content_length
>= 0) {
507 theSize
= content_length
;
517 * Create a Request from a URL and METHOD.
519 * If the METHOD is CONNECT, then a host:port pair is looked for instead of a URL.
520 * If the request cannot be created cleanly, NULL is returned
523 HttpRequest::FromUrl(const char * url
, const MasterXaction::Pointer
&mx
, const HttpRequestMethod
& method
)
525 std::unique_ptr
<HttpRequest
> req(new HttpRequest(mx
));
526 if (req
->url
.parse(method
, url
)) {
527 req
->method
= method
;
528 return req
.release();
534 * Are responses to this request possible cacheable ?
535 * If false then no matter what the response must not be cached.
538 HttpRequest::maybeCacheable()
540 // Intercepted request with Host: header which cannot be trusted.
541 // Because it failed verification, or someone bypassed the security tests
542 // we cannot cache the reponse for sharing between clients.
543 // TODO: update cache to store for particular clients only (going to same Host: and destination IP)
544 if (!flags
.hostVerified
&& (flags
.intercepted
|| flags
.interceptTproxy
))
547 switch (url
.getScheme()) {
548 case AnyP::PROTO_HTTP
:
549 case AnyP::PROTO_HTTPS
:
550 if (!method
.respMaybeCacheable())
553 // RFC 7234 section 5.2.1.5:
554 // "cache MUST NOT store any part of either this request or any response to it"
556 // NP: refresh_pattern ignore-no-store only applies to response messages
557 // this test is handling request message CC header.
558 if (!flags
.ignoreCc
&& cache_control
&& cache_control
->hasNoStore())
562 case AnyP::PROTO_GOPHER
:
563 if (!gopherCachable(this))
567 case AnyP::PROTO_CACHE_OBJECT
:
570 //case AnyP::PROTO_FTP:
579 HttpRequest::conditional() const
582 header
.has(Http::HdrType::IF_MATCH
) ||
583 header
.has(Http::HdrType::IF_NONE_MATCH
);
587 HttpRequest::recordLookup(const Dns::LookupDetails
&dns
)
589 if (dns
.wait
>= 0) { // known delay
590 if (dnsWait
>= 0) { // have recorded DNS wait before
591 debugs(78, 7, this << " " << dnsWait
<< " += " << dns
);
594 debugs(78, 7, this << " " << dns
);
601 HttpRequest::getRangeOffsetLimit()
603 /* -2 is the starting value of rangeOffsetLimit.
604 * If it is -2, that means we haven't checked it yet.
605 * Otherwise, return the current value */
606 if (rangeOffsetLimit
!= -2)
607 return rangeOffsetLimit
;
609 rangeOffsetLimit
= 0; // default value for rangeOffsetLimit
611 ACLFilledChecklist
ch(NULL
, this, NULL
);
612 ch
.src_addr
= client_addr
;
613 ch
.my_addr
= my_addr
;
615 for (AclSizeLimit
*l
= Config
.rangeOffsetLimit
; l
; l
= l
-> next
) {
616 /* if there is no ACL list or if the ACLs listed match use this limit value */
617 if (!l
->aclList
|| ch
.fastCheck(l
->aclList
).allowed()) {
618 debugs(58, 4, HERE
<< "rangeOffsetLimit=" << rangeOffsetLimit
);
619 rangeOffsetLimit
= l
->size
; // may be -1
624 return rangeOffsetLimit
;
628 HttpRequest::ignoreRange(const char *reason
)
631 debugs(73, 3, static_cast<void*>(range
) << " for " << reason
);
635 // Some callers also reset isRanged but it may not be safe for all callers:
636 // isRanged is used to determine whether a weak ETag comparison is allowed,
637 // and that check should not ignore the Range header if it was present.
638 // TODO: Some callers also delete HDR_RANGE, HDR_REQUEST_RANGE. Should we?
642 HttpRequest::canHandle1xx() const
644 // old clients do not support 1xx unless they sent Expect: 100-continue
645 // (we reject all other Http::HdrType::EXPECT values so just check for Http::HdrType::EXPECT)
646 if (http_ver
<= Http::ProtocolVersion(1,0) && !header
.has(Http::HdrType::EXPECT
))
649 // others must support 1xx control messages
654 HttpRequest::pinnedConnection()
656 if (clientConnectionManager
.valid() && clientConnectionManager
->pinning
.pinned
)
657 return clientConnectionManager
.get();
662 HttpRequest::storeId()
664 if (store_id
.size() != 0) {
665 debugs(73, 3, "sent back store_id: " << store_id
);
666 return StringToSBuf(store_id
);
668 debugs(73, 3, "sent back effectiveRequestUrl: " << effectiveRequestUri());
669 return effectiveRequestUri();
673 HttpRequest::effectiveRequestUri() const
675 if (method
.id() == Http::METHOD_CONNECT
|| url
.getScheme() == AnyP::PROTO_AUTHORITY_FORM
)
676 return url
.authority(true); // host:port
677 return url
.absolute();
684 theNotes
= new NotePairs
;
689 UpdateRequestNotes(ConnStateData
*csd
, HttpRequest
&request
, NotePairs
const &helperNotes
)
691 // Tag client connection if the helper responded with clt_conn_tag=tag.
692 const char *cltTag
= "clt_conn_tag";
693 if (const char *connTag
= helperNotes
.findFirst(cltTag
)) {
695 csd
->notes()->remove(cltTag
);
696 csd
->notes()->add(cltTag
, connTag
);
699 request
.notes()->replaceOrAdd(&helperNotes
);
703 HttpRequest::manager(const CbcPointer
<ConnStateData
> &aMgr
, const AccessLogEntryPointer
&al
)
705 clientConnectionManager
= aMgr
;
707 if (!clientConnectionManager
.valid())
710 AnyP::PortCfgPointer port
= clientConnectionManager
->port
;
712 myportname
= port
->name
;
713 flags
.ignoreCc
= port
->ignore_cc
;
716 if (auto clientConnection
= clientConnectionManager
->clientConnection
) {
717 client_addr
= clientConnection
->remote
; // XXX: remove request->client_addr member.
718 #if FOLLOW_X_FORWARDED_FOR
719 // indirect client gets stored here because it is an HTTP header result (from X-Forwarded-For:)
720 // not details about the TCP connection itself
721 indirect_client_addr
= clientConnection
->remote
;
722 #endif /* FOLLOW_X_FORWARDED_FOR */
723 my_addr
= clientConnection
->local
;
725 flags
.intercepted
= ((clientConnection
->flags
& COMM_INTERCEPTION
) != 0);
726 flags
.interceptTproxy
= ((clientConnection
->flags
& COMM_TRANSPARENT
) != 0 ) ;
727 const bool proxyProtocolPort
= port
? port
->flags
.proxySurrogate
: false;
728 if (flags
.interceptTproxy
&& !proxyProtocolPort
) {
729 if (Config
.accessList
.spoof_client_ip
) {
730 ACLFilledChecklist
*checklist
= new ACLFilledChecklist(Config
.accessList
.spoof_client_ip
, this, clientConnection
->rfc931
);
732 flags
.spoofClientIp
= checklist
->fastCheck().allowed();
735 flags
.spoofClientIp
= true;
737 flags
.spoofClientIp
= false;