Squid Web Cache master
Loading...
Searching...
No Matches
Uri.cc
Go to the documentation of this file.
1/*
2 * Copyright (C) 1996-2026 The Squid Software Foundation and contributors
3 *
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.
7 */
8
9/* DEBUG: section 23 URL Parsing */
10
11#include "squid.h"
12#include "anyp/Host.h"
13#include "anyp/Uri.h"
14#include "base/Raw.h"
15#include "globals.h"
16#include "HttpRequest.h"
17#include "parser/Tokenizer.h"
18#include "rfc1738.h"
19#include "SquidConfig.h"
20#include "SquidMath.h"
21
22static const char valid_hostname_chars_u[] =
23 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
24 "abcdefghijklmnopqrstuvwxyz"
25 "0123456789-._"
26 "[:]"
27 ;
28static const char valid_hostname_chars[] =
29 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
30 "abcdefghijklmnopqrstuvwxyz"
31 "0123456789-."
32 "[:]"
33 ;
34
36static const CharacterSet &
38{
39 /*
40 * RFC 3986 section 3.2.1
41 *
42 * userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
43 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
44 * pct-encoded = "%" HEXDIG HEXDIG
45 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
46 */
47 static const auto userInfoValid = CharacterSet("userinfo", ":-._~%!$&'()*+,;=") +
50 return userInfoValid;
51}
52
54static const CharacterSet &
56{
57 /*
58 * RFC 3986 section 3.3
59 *
60 * path = path-abempty ; begins with "/" or is empty
61 * ...
62 * path-abempty = *( "/" segment )
63 * segment = *pchar
64 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
65 */
66 static const auto pathValid = CharacterSet("path", "/:@-._~%!$&'()*+,;=") +
69 return pathValid;
70}
71
75SBuf
76AnyP::Uri::Encode(const SBuf &buf, const CharacterSet &ignore)
77{
78 if (buf.isEmpty())
79 return buf;
80
81 Parser::Tokenizer tk(buf);
82 SBuf goodSection;
83 // optimization for the arguably common "no encoding necessary" case
84 if (tk.prefix(goodSection, ignore) && tk.atEnd())
85 return buf;
86
87 SBuf output;
88 output.reserveSpace(buf.length() * 3); // worst case: encode all chars
89 output.append(goodSection); // may be empty
90
91 while (!tk.atEnd()) {
92 // TODO: Add Tokenizer::parseOne(void).
93 const auto ch = tk.remaining()[0];
94 output.appendf("%%%02X", static_cast<unsigned int>(static_cast<unsigned char>(ch))); // TODO: Optimize using a table
95 (void)tk.skip(ch);
96
97 if (tk.prefix(goodSection, ignore))
98 output.append(goodSection);
99 }
100
101 return output;
102}
103
104std::optional<SBuf>
106{
107 SBuf output;
109 while (!tok.atEnd()) {
110 SBuf token;
111 static const auto unencodedChars = CharacterSet("percent", "%").complement("unencoded");
112 if (tok.prefix(token, unencodedChars))
113 output.append(token);
114
115 // we are either at '%' or at end of input
116 if (tok.skip('%')) {
117 const auto rawBytesAfterPercent = tok.remaining();
118 int64_t hex1 = 0, hex2 = 0;
119 // The last part of the condition below rejects syntactically valid
120 // %00 triplets. RFC 3986 arguably permits such special treatment in
121 // Squid: 'the "%00" percent-encoding (NUL) may require special
122 // handling and should be rejected if the application is not
123 // expecting to receive raw data within a component'.
124 if (tok.int64(hex1, 16, false, 1) && tok.int64(hex2, 16, false, 1) && (hex1 || hex2)) {
125 output.append(static_cast<char>((hex1 << 4) | hex2));
126 } else {
127 // see TestUri::testEncoding() for invalid pct-encoding sequence examples
128 debugs(23, 3, "invalid pct-encoding sequence starting at %" << rawBytesAfterPercent);
129 return std::nullopt;
130 }
131 }
132 }
133 return output;
134}
135
136SBuf
138{
139 if (const auto decoded = Decode(input))
140 return *decoded;
141 return input;
142}
143
144const SBuf &
146{
147 static SBuf star("*");
148 return star;
149}
150
151const SBuf &
153{
154 static SBuf slash("/");
155 return slash;
156}
157
158void
159AnyP::Uri::host(const char *src)
160{
161 hostAddr_.fromHost(src);
162 if (hostAddr_.isAnyAddr()) {
163 xstrncpy(host_, src, sizeof(host_));
164 hostIsNumeric_ = false;
165 } else {
166 hostAddr_.toHostStr(host_, sizeof(host_));
167 debugs(23, 3, "given IP: " << hostAddr_);
168 hostIsNumeric_ = 1;
169 }
170 touch();
171}
172
173// TODO: Replace with ToSBuf(parsedHost()) or similar.
174SBuf
176{
177 if (hostIsNumeric()) {
178 static char ip[MAX_IPSTRLEN];
179 const auto hostStrLen = hostIP().toHostStr(ip, sizeof(ip));
180 return SBuf(ip, hostStrLen);
181 } else
182 return SBuf(host());
183}
184
185std::optional<AnyP::Host>
187{
188 if (hostIsNumeric())
189 return Host::ParseIp(hostIP());
190
191 // XXX: Interpret host subcomponent as reg-name representing a DNS name. It
192 // may actually be, for example, a URN namespace ID (NID; see RFC 8141), but
193 // current Squid APIs do not support adequate representation of those cases.
194 const SBuf regName(host());
195
196 if (regName.find('%') != SBuf::npos) {
197 debugs(23, 3, "rejecting percent-encoded reg-name: " << regName);
198 return std::nullopt; // TODO: Decode() instead
199 }
200
201 return Host::ParseSimpleDomainName(regName);
202}
203
204const SBuf &
206{
207 // RFC 3986 section 3.3 says path can be empty (path-abempty).
208 // RFC 7230 sections 2.7.3, 5.3.1, 5.7.2 - says path cannot be empty, default to "/"
209 // at least when sending and using. We must still accept path-abempty as input.
210 if (path_.isEmpty() && (scheme_ == AnyP::PROTO_HTTP || scheme_ == AnyP::PROTO_HTTPS))
211 return SlashPath();
212
213 return path_;
214}
215
216void
218{
219 debugs(23, 5, "urlInitialize: Initializing...");
220 /* this ensures that the number of protocol strings is the same as
221 * the enum slots allocated because the last enum is always 'MAX'.
222 */
223 assert(strcmp(AnyP::ProtocolType_str[AnyP::PROTO_MAX], "MAX") == 0);
224 /*
225 * These test that our matchDomainName() function works the
226 * way we expect it to.
227 */
228 assert(0 == matchDomainName("foo.com", "foo.com"));
229 assert(0 == matchDomainName(".foo.com", "foo.com"));
230 assert(0 == matchDomainName("foo.com", ".foo.com"));
231 assert(0 == matchDomainName(".foo.com", ".foo.com"));
232 assert(0 == matchDomainName("x.foo.com", ".foo.com"));
233 assert(0 == matchDomainName("y.x.foo.com", ".foo.com"));
234 assert(0 != matchDomainName("x.foo.com", "foo.com"));
235 assert(0 != matchDomainName("foo.com", "x.foo.com"));
236 assert(0 != matchDomainName("bar.com", "foo.com"));
237 assert(0 != matchDomainName(".bar.com", "foo.com"));
238 assert(0 != matchDomainName(".bar.com", ".foo.com"));
239 assert(0 != matchDomainName("bar.com", ".foo.com"));
240 assert(0 < matchDomainName("zzz.com", "foo.com"));
241 assert(0 > matchDomainName("aaa.com", "foo.com"));
242 assert(0 == matchDomainName("FOO.com", "foo.COM"));
243 assert(0 < matchDomainName("bfoo.com", "afoo.com"));
244 assert(0 > matchDomainName("afoo.com", "bfoo.com"));
245 assert(0 < matchDomainName("x-foo.com", ".foo.com"));
246
247 assert(0 == matchDomainName(".foo.com", ".foo.com", mdnRejectSubsubDomains));
248 assert(0 == matchDomainName("x.foo.com", ".foo.com", mdnRejectSubsubDomains));
249 assert(0 != matchDomainName("y.x.foo.com", ".foo.com", mdnRejectSubsubDomains));
250 assert(0 != matchDomainName(".x.foo.com", ".foo.com", mdnRejectSubsubDomains));
251
252 assert(0 == matchDomainName("*.foo.com", "x.foo.com", mdnHonorWildcards));
253 assert(0 == matchDomainName("*.foo.com", ".x.foo.com", mdnHonorWildcards));
254 assert(0 == matchDomainName("*.foo.com", ".foo.com", mdnHonorWildcards));
255 assert(0 != matchDomainName("*.foo.com", "foo.com", mdnHonorWildcards));
256
257 assert(0 != matchDomainName("foo.com", ""));
258 assert(0 != matchDomainName("foo.com", "", mdnHonorWildcards));
259 assert(0 != matchDomainName("foo.com", "", mdnRejectSubsubDomains));
260
261 /* more cases? */
262}
263
271static AnyP::UriScheme
273{
274 /*
275 * RFC 3986 section 3.1 paragraph 2:
276 *
277 * Scheme names consist of a sequence of characters beginning with a
278 * letter and followed by any combination of letters, digits, plus
279 * ("+"), period ("."), or hyphen ("-").
280 */
281 static const auto schemeChars = CharacterSet("scheme", "+.-") + CharacterSet::ALPHA + CharacterSet::DIGIT;
282
283 SBuf str;
284 if (tok.prefix(str, schemeChars, 16) && tok.skip(':') && CharacterSet::ALPHA[str.at(0)]) {
285 const auto protocol = AnyP::UriScheme::FindProtocolType(str);
286 if (protocol == AnyP::PROTO_UNKNOWN)
287 return AnyP::UriScheme(protocol, str.c_str());
288 return AnyP::UriScheme(protocol, nullptr);
289 }
290
291 throw TextException("invalid URI scheme", Here());
292}
293
301bool
303{
304 /* For IPv4 addresses check for a dot */
305 /* For IPv6 addresses also check for a colon */
306 if (Config.appendDomain && !strchr(host, '.') && !strchr(host, ':')) {
307 const uint64_t dlen = strlen(host);
308 const uint64_t want = dlen + Config.appendDomainLen;
309 if (want > SQUIDHOSTNAMELEN - 1) {
310 debugs(23, 2, "URL domain too large (" << dlen << " bytes)");
311 return false;
312 }
313 strncat(host, Config.appendDomain, SQUIDHOSTNAMELEN - dlen - 1);
314 }
315 return true;
316}
317
318/*
319 * Parse a URI/URL.
320 *
321 * It is assumed that the URL is complete -
322 * ie, the end of the string is the end of the URL. Don't pass a partial
323 * URL here as this routine doesn't have any way of knowing whether
324 * it is partial or not (ie, it handles the case of no trailing slash as
325 * being "end of host with implied path of /".
326 *
327 * method is used to switch parsers. If method is Http::METHOD_CONNECT,
328 * then rather than a URL a hostname:port is looked for.
329 */
330bool
331AnyP::Uri::parse(const HttpRequestMethod& method, const SBuf &rawUrl)
332{
333 try {
334
335 LOCAL_ARRAY(char, login, MAX_URL);
336 LOCAL_ARRAY(char, foundHost, MAX_URL);
337 LOCAL_ARRAY(char, urlpath, MAX_URL);
338 char *t = nullptr;
339 char *q = nullptr;
340 int foundPort;
341 int l;
342 int i;
343 const char *src;
344 char *dst;
345 foundHost[0] = urlpath[0] = login[0] = '\0';
346
347 if ((l = rawUrl.length()) + Config.appendDomainLen > (MAX_URL - 1)) {
348 debugs(23, DBG_IMPORTANT, MYNAME << "URL too large (" << l << " bytes)");
349 return false;
350 }
351
352 if ((method == Http::METHOD_OPTIONS || method == Http::METHOD_TRACE) &&
353 Asterisk().cmp(rawUrl) == 0) {
354 // XXX: these methods might also occur in HTTPS traffic. Handle this better.
355 setScheme(AnyP::PROTO_HTTP, nullptr);
356 port(getScheme().defaultPort());
357 path(Asterisk());
358 return true;
359 }
360
361 Parser::Tokenizer tok(rawUrl);
362 AnyP::UriScheme scheme;
363
364 if (method == Http::METHOD_CONNECT) {
365 // For CONNECTs, RFC 9110 Section 9.3.6 requires "only the host and
366 // port number of the tunnel destination, separated by a colon".
367
368 const auto rawHost = parseHost(tok);
369 Assure(rawHost.length() < sizeof(foundHost));
370 SBufToCstring(foundHost, rawHost);
371
372 if (!tok.skip(':'))
373 throw TextException("missing required :port in CONNECT target", Here());
374 foundPort = parsePort(tok);
375
376 if (!tok.remaining().isEmpty())
377 throw TextException("garbage after host:port in CONNECT target", Here());
378 } else {
379
380 scheme = uriParseScheme(tok);
381
382 if (scheme == AnyP::PROTO_NONE)
383 return false; // invalid scheme
384
385 if (scheme == AnyP::PROTO_URN) {
386 parseUrn(tok); // throws on any error
387 return true;
388 }
389
390 // URLs then have "//"
391 static const SBuf doubleSlash("//");
392 if (!tok.skip(doubleSlash))
393 return false;
394
395 auto B = tok.remaining();
396 const char *url = B.c_str();
397
398 /* Parse the URL: */
399 src = url;
400 i = 0;
401
402 /* Then everything until first /; that's host (and port; which we'll look for here later) */
403 // bug 1881: If we don't get a "/" then we imply it was there
404 // bug 3074: We could just be given a "?" or "#". These also imply "/"
405 // bug 3233: whitespace is also a hostname delimiter.
406 for (dst = foundHost; i < l && *src != '/' && *src != '?' && *src != '#' && *src != '\0' && !xisspace(*src); ++i, ++src, ++dst) {
407 *dst = *src;
408 }
409
410 /*
411 * We can't check for "i >= l" here because we could be at the end of the line
412 * and have a perfectly valid URL w/ no trailing '/'. In this case we assume we've
413 * been -given- a valid URL and the path is just '/'.
414 */
415 if (i > l)
416 return false;
417 *dst = '\0';
418
419 // We are looking at path-abempty.
420 if (*src != '/') {
421 // path-empty, including the end of the `src` c-string cases
422 urlpath[0] = '/';
423 dst = &urlpath[1];
424 } else {
425 dst = urlpath;
426 }
427 /* Then everything from / (inclusive) until \r\n or \0 - that's urlpath */
428 for (; i < l && *src != '\r' && *src != '\n' && *src != '\0'; ++i, ++src, ++dst) {
429 *dst = *src;
430 }
431
432 /* We -could- be at the end of the buffer here */
433 if (i > l)
434 return false;
435 *dst = '\0';
436
437 // If the parsed scheme has no (known) default port, and there is no
438 // explicit port, then we will reject the zero port during foundPort
439 // validation, often resulting in a misleading 400/ERR_INVALID_URL.
440 // TODO: Remove this hack when switching to Tokenizer-based parsing.
441 foundPort = scheme.defaultPort().value_or(0); // may be reset later
442
443 /* Is there any login information? (we should eventually parse it above) */
444 t = strrchr(foundHost, '@');
445 if (t != nullptr) {
446 strncpy((char *) login, (char *) foundHost, sizeof(login)-1);
447 login[sizeof(login)-1] = '\0';
448 t = strrchr(login, '@');
449 *t = 0;
450 strncpy((char *) foundHost, t + 1, sizeof(foundHost)-1);
451 foundHost[sizeof(foundHost)-1] = '\0';
452 // Bug 4498: URL-unescape the login info after extraction
453 rfc1738_unescape(login);
454 }
455
456 /* Is there any host information? (we should eventually parse it above) */
457 if (*foundHost == '[') {
458 /* strip any IPA brackets. valid under IPv6. */
459 dst = foundHost;
460 /* only for IPv6 sadly, pre-IPv6/URL code can't handle the clean result properly anyway. */
461 src = foundHost;
462 ++src;
463 l = strlen(foundHost);
464 i = 1;
465 for (; i < l && *src != ']' && *src != '\0'; ++i, ++src, ++dst) {
466 *dst = *src;
467 }
468
469 /* we moved in-place, so truncate the actual hostname found */
470 *dst = '\0';
471 ++dst;
472
473 /* skip ahead to either start of port, or original EOS */
474 while (*dst != '\0' && *dst != ':')
475 ++dst;
476 t = dst;
477 } else {
478 t = strrchr(foundHost, ':');
479
480 if (t != strchr(foundHost,':') ) {
481 /* RFC 2732 states IPv6 "SHOULD" be bracketed. allowing for times when its not. */
482 /* RFC 3986 'update' simply modifies this to an "is" with no emphasis at all! */
483 /* therefore we MUST accept the case where they are not bracketed at all. */
484 t = nullptr;
485 }
486 }
487
488 // Bug 3183 sanity check: If scheme is present, host must be too.
489 if (scheme != AnyP::PROTO_NONE && foundHost[0] == '\0') {
490 debugs(23, DBG_IMPORTANT, "SECURITY ALERT: Missing hostname in URL '" << url << "'. see access.log for details.");
491 return false;
492 }
493
494 if (t && *t == ':') {
495 *t = '\0';
496 ++t;
497 foundPort = atoi(t);
498 }
499 }
500
501 for (t = foundHost; *t; ++t)
502 *t = xtolower(*t);
503
504 if (stringHasWhitespace(foundHost)) {
506 t = q = foundHost;
507 while (*t) {
508 if (!xisspace(*t)) {
509 *q = *t;
510 ++q;
511 }
512 ++t;
513 }
514 *q = '\0';
515 }
516 }
517
518 debugs(23, 3, "Split URL '" << rawUrl << "' into proto='" << scheme.image() << "', host='" << foundHost << "', port='" << foundPort << "', path='" << urlpath << "'");
519
521 strspn(foundHost, Config.onoff.allow_underscore ? valid_hostname_chars_u : valid_hostname_chars) != strlen(foundHost)) {
522 debugs(23, DBG_IMPORTANT, MYNAME << "Illegal character in hostname '" << foundHost << "'");
523 return false;
524 }
525
526 if (!urlAppendDomain(foundHost))
527 return false;
528
529 /* remove trailing dots from hostnames */
530 while ((l = strlen(foundHost)) > 0 && foundHost[--l] == '.')
531 foundHost[l] = '\0';
532
533 /* reject duplicate or leading dots */
534 if (strstr(foundHost, "..") || *foundHost == '.') {
535 debugs(23, DBG_IMPORTANT, MYNAME << "Illegal hostname '" << foundHost << "'");
536 return false;
537 }
538
539 if (foundPort < 1 || foundPort > 65535) {
540 debugs(23, 3, "Invalid port '" << foundPort << "'");
541 return false;
542 }
543
544 if (stringHasWhitespace(urlpath)) {
545 debugs(23, 2, "URI has whitespace: {" << rawUrl << "}");
546
547 switch (Config.uri_whitespace) {
548
550 return false;
551
553 break;
554
556 t = rfc1738_escape_unescaped(urlpath);
557 xstrncpy(urlpath, t, MAX_URL);
558 break;
559
561 *(urlpath + strcspn(urlpath, w_space)) = '\0';
562 break;
563
565 default:
566 t = q = urlpath;
567 while (*t) {
568 if (!xisspace(*t)) {
569 *q = *t;
570 ++q;
571 }
572 ++t;
573 }
574 *q = '\0';
575 }
576 }
577
578 setScheme(scheme);
579 path(urlpath);
580 host(foundHost);
581 userInfo(SBuf(login));
582 port(foundPort);
583 return true;
584
585 } catch (...) {
586 debugs(23, 2, "error: " << CurrentException << " " << Raw("rawUrl", rawUrl.rawContent(), rawUrl.length()));
587 return false;
588 }
589}
590
605void
607{
608 static const auto nidChars = CharacterSet("NID","-") + CharacterSet::ALPHA + CharacterSet::DIGIT;
609 static const auto alphanum = (CharacterSet::ALPHA + CharacterSet::DIGIT).rename("alphanum");
610 SBuf nid;
611 if (!tok.prefix(nid, nidChars, 32))
612 throw TextException("NID not found", Here());
613
614 if (!tok.skip(':'))
615 throw TextException("NID too long or missing ':' delimiter", Here());
616
617 if (nid.length() < 2)
618 throw TextException("NID too short", Here());
619
620 if (!alphanum[*nid.begin()])
621 throw TextException("NID prefix is not alphanumeric", Here());
622
623 if (!alphanum[*nid.rbegin()])
624 throw TextException("NID suffix is not alphanumeric", Here());
625
626 setScheme(AnyP::PROTO_URN, nullptr);
627 host(nid.c_str());
628 // TODO validate path characters
629 path(tok.remaining());
630 debugs(23, 3, "Split URI into proto=urn, nid=" << nid << ", " << Raw("path",path().rawContent(),path().length()));
631}
632
636SBuf
638{
639 // host = IP-literal / IPv4address / reg-name
640
641 // XXX: CharacterSets below reject uri-host values containing whitespace
642 // (e.g., "10.0.0. 1"). That is not a bug, but the uri_whitespace directive
643 // can be interpreted as if it applies to uri-host and this code. TODO: Fix
644 // uri_whitespace and the code using it to exclude uri-host (and URI scheme,
645 // port, etc.) from that directive scope.
646
647 // IP-literal = "[" ( IPv6address / IPvFuture ) "]"
648 if (tok.skip('[')) {
649 // Add "." because IPv6address in RFC 3986 includes ls32, which includes
650 // IPv4address: ls32 = ( h16 ":" h16 ) / IPv4address
651 // This set rejects IPvFuture that needs a "v" character.
652 static const CharacterSet IPv6chars = (
653 CharacterSet::HEXDIG + CharacterSet("colon", ":") + CharacterSet("period", ".")).rename("IPv6");
654 SBuf ipv6ish;
655 if (!tok.prefix(ipv6ish, IPv6chars))
656 throw TextException("malformed or unsupported bracketed IP address in uri-host", Here());
657
658 if (!tok.skip(']'))
659 throw TextException("IPv6 address is missing a closing bracket in uri-host", Here());
660
661 // This rejects bracketed IPv4address and domain names because they lack ":".
662 if (ipv6ish.find(':') == SBuf::npos)
663 throw TextException("bracketed IPv6 address is missing a colon in uri-host", Here());
664
665 // This rejects bracketed non-IP addresses that our caller would have
666 // otherwise mistaken for a domain name (e.g., '[127.0.0:1]').
667 Ip::Address ipv6check;
668 if (!ipv6check.fromHost(ipv6ish.c_str()))
669 throw TextException("malformed bracketed IPv6 address in uri-host", Here());
670
671 return ipv6ish;
672 }
673
674 // no brackets implies we are looking at IPv4address or reg-name
675
676 // XXX: This code does not detect/reject some bad host values (e.g. `!#$%&`).
677 // TODO: Add more checks here, after migrating the
678 // non-CONNECT uri-host parsing code to use us.
679
680 SBuf otherHost; // IPv4address-ish or reg-name-ish
681 // ":" is not in TCHAR so we will stop before any port specification
682 if (tok.prefix(otherHost, CharacterSet::TCHAR))
683 return otherHost;
684
685 throw TextException("malformed IPv4 address or host name in uri-host", Here());
686}
687
694int
696{
697 if (tok.skip('0'))
698 throw TextException("zero or zero-prefixed port", Here());
699
700 int64_t rawPort = 0;
701 if (!tok.int64(rawPort, 10, false)) // port = *DIGIT
702 throw TextException("malformed or missing port", Here());
703
704 Assure(rawPort > 0);
705 constexpr KnownPort portMax = 65535; // TODO: Make this a class-scope constant and REuse it.
706 constexpr auto portStorageMax = std::numeric_limits<Port::value_type>::max();
707 static_assert(!Less(portStorageMax, portMax), "Port type can represent the maximum valid port number");
708 if (Less(portMax, rawPort))
709 throw TextException("huge port", Here());
710
711 // TODO: Return KnownPort after migrating the non-CONNECT uri-host parsing
712 // code to use us (so that foundPort "int" disappears or starts using Port).
713 return NaturalCast<int>(rawPort);
714}
715
716void
718{
719 absolute_.clear();
720 authorityHttp_.clear();
721 authorityWithPort_.clear();
722 absolutePath_.clear();
723}
724
725SBuf &
726AnyP::Uri::authority(bool requirePort) const
727{
728 if (authorityHttp_.isEmpty()) {
729
730 // both formats contain Host/IP
731 authorityWithPort_.append(host());
732 authorityHttp_ = authorityWithPort_;
733
734 if (port().has_value()) {
735 authorityWithPort_.appendf(":%hu", *port());
736 // authorityHttp_ only has :port for known non-default ports
737 if (port() != getScheme().defaultPort())
738 authorityHttp_ = authorityWithPort_;
739 }
740 // else XXX: We made authorityWithPort_ that does not have a port.
741 // TODO: Audit callers and refuse to give out broken authorityWithPort_.
742 }
743
744 return requirePort ? authorityWithPort_ : authorityHttp_;
745}
746
747SBuf &
749{
750 if (absolute_.isEmpty()) {
751 // TODO: most URL will be much shorter, avoid allocating this much
752 absolute_.reserveCapacity(MAX_URL);
753
754 absolute_.append(getScheme().image());
755 absolute_.append(":",1);
756 if (getScheme() != AnyP::PROTO_URN) {
757 absolute_.append("//", 2);
758 const bool allowUserInfo = getScheme() == AnyP::PROTO_FTP ||
759 getScheme() == AnyP::PROTO_UNKNOWN;
760
761 if (allowUserInfo && !userInfo().isEmpty()) {
762 static const CharacterSet uiChars = CharacterSet(UserInfoChars())
763 .remove('%')
764 .rename("userinfo-reserved");
765 absolute_.append(Encode(userInfo(), uiChars));
766 absolute_.append("@", 1);
767 }
768 absolute_.append(authority());
769 } else {
770 absolute_.append(host());
771 absolute_.append(":", 1);
772 }
773 absolute_.append(absolutePath());
774 }
775
776 return absolute_;
777}
778
779SBuf &
781{
782 if (absolutePath_.isEmpty()) {
783 // TODO: Encode each URI subcomponent in path_ as needed.
784 absolutePath_ = Encode(path(), PathChars());
785 }
786
787 return absolutePath_;
788}
789
790/* XXX: Performance: This is an *almost* duplicate of HttpRequest::effectiveRequestUri(). But elides the query-string.
791 * After copying it on in the first place! Would be less code to merge the two with a flag parameter.
792 * and never copy the query-string part in the first place
793 */
794char *
796{
797 LOCAL_ARRAY(char, buf, MAX_URL);
798
799 snprintf(buf, sizeof(buf), SQUIDSBUFPH, SQUIDSBUFPRINT(url));
800 buf[sizeof(buf)-1] = '\0';
801
802 // URN, CONNECT method, and non-stripped URIs can go straight out
803 if (Config.onoff.strip_query_terms && !(method == Http::METHOD_CONNECT || scheme == AnyP::PROTO_URN)) {
804 // strip anything AFTER a question-mark
805 // leaving the '?' in place
806 if (auto t = strchr(buf, '?')) {
807 *(++t) = '\0';
808 }
809 }
810
811 if (stringHasCntl(buf))
813
814 return buf;
815}
816
823const char *
825{
826 LOCAL_ARRAY(char, buf, MAX_URL);
827
828 // method CONNECT and port HTTPS
829 if (request->method == Http::METHOD_CONNECT && request->url.port() == 443) {
830 snprintf(buf, MAX_URL, "https://%s/*", request->url.host());
831 return buf;
832 }
833
834 // else do the normal complete canonical thing.
835 return request->canonicalCleanUrl();
836}
837
850bool
851urlIsRelative(const char *url)
852{
853 if (!url)
854 return false; // no URL
855
856 /*
857 * RFC 3986 section 5.2.3
858 *
859 * path = path-abempty ; begins with "/" or is empty
860 * / path-absolute ; begins with "/" but not "//"
861 * / path-noscheme ; begins with a non-colon segment
862 * / path-rootless ; begins with a segment
863 * / path-empty ; zero characters
864 */
865
866 if (*url == '\0')
867 return true; // path-empty
868
869 if (*url == '/') {
870 // network-path reference (a.k.a. 'scheme-relative URI') or
871 // path-absolute (a.k.a. 'absolute-path reference')
872 return true;
873 }
874
875 for (const auto *p = url; *p != '\0' && *p != '/' && *p != '?' && *p != '#'; ++p) {
876 if (*p == ':')
877 return false; // colon is forbidden in first segment
878 }
879
880 return true; // path-noscheme, path-abempty, path-rootless
881}
882
883void
884AnyP::Uri::addRelativePath(const char *relUrl)
885{
886 // URN cannot be merged
887 if (getScheme() == AnyP::PROTO_URN)
888 return;
889
890 // TODO: Handle . and .. segment normalization
891
892 const auto lastSlashPos = path_.rfind('/');
893 // TODO: To optimize and simplify, add and use SBuf::replace().
894 const auto relUrlLength = strlen(relUrl);
895 if (lastSlashPos == SBuf::npos) {
896 // start replacing the whole path
897 path_.reserveCapacity(1 + relUrlLength);
898 path_.assign("/", 1);
899 } else {
900 // start replacing just the last segment
901 path_.reserveCapacity(lastSlashPos + 1 + relUrlLength);
902 path_.chop(0, lastSlashPos+1);
903 }
904 path_.append(relUrl, relUrlLength);
905}
906
907int
908matchDomainName(const char *h, const char *d, MatchDomainNameFlags flags)
909{
910 int dl;
911 int hl;
912
913 const bool hostIncludesSubdomains = (*h == '.');
914 while ('.' == *h)
915 ++h;
916
917 hl = strlen(h);
918
919 if (hl == 0)
920 return -1;
921
922 dl = strlen(d);
923 if (dl == 0)
924 return 1;
925
926 /*
927 * Start at the ends of the two strings and work towards the
928 * beginning.
929 */
930 while (xtolower(h[--hl]) == xtolower(d[--dl])) {
931 if (hl == 0 && dl == 0) {
932 /*
933 * We made it all the way to the beginning of both
934 * strings without finding any difference.
935 */
936 return 0;
937 }
938
939 if (0 == hl) {
940 /*
941 * The host string is shorter than the domain string.
942 * There is only one case when this can be a match.
943 * If the domain is just one character longer, and if
944 * that character is a leading '.' then we call it a
945 * match.
946 */
947
948 if (1 == dl && '.' == d[0])
949 return 0;
950 else
951 return -1;
952 }
953
954 if (0 == dl) {
955 /*
956 * The domain string is shorter than the host string.
957 * This is a match only if the first domain character
958 * is a leading '.'.
959 */
960
961 if ('.' == d[0]) {
962 if (flags & mdnRejectSubsubDomains) {
963 // Check for sub-sub domain and reject
964 while(--hl >= 0 && h[hl] != '.');
965 if (hl < 0) {
966 // No sub-sub domain found, but reject if there is a
967 // leading dot in given host string (which is removed
968 // before the check is started).
969 return hostIncludesSubdomains ? 1 : 0;
970 } else
971 return 1; // sub-sub domain, reject
972 } else
973 return 0;
974 } else
975 return 1;
976 }
977 }
978
979 /*
980 * We found different characters in the same position (from the end).
981 */
982
983 // If the h has a form of "*.foo.com" and d has a form of "x.foo.com"
984 // then the h[hl] points to '*', h[hl+1] to '.' and d[dl] to 'x'
985 // The following checks are safe, the "h[hl + 1]" in the worst case is '\0'.
986 if ((flags & mdnHonorWildcards) && h[hl] == '*' && h[hl + 1] == '.')
987 return 0;
988
989 /*
990 * If one of those character is '.' then its special. In order
991 * for splay tree sorting to work properly, "x-foo.com" must
992 * be greater than ".foo.com" even though '-' is less than '.'.
993 */
994 if ('.' == d[dl])
995 return 1;
996
997 if ('.' == h[hl])
998 return -1;
999
1000 return (xtolower(h[hl]) - xtolower(d[dl]));
1001}
1002
1003/*
1004 * return true if we can serve requests for this method.
1005 */
1006bool
1008{
1009 /* protocol "independent" methods
1010 *
1011 * actually these methods are specific to HTTP:
1012 * they are methods we receive on our HTTP port,
1013 * and if we had a FTP listener would not be relevant
1014 * there.
1015 *
1016 * So, we should delegate them to HTTP. The problem is that we
1017 * do not have a default protocol from the client side of HTTP.
1018 */
1019
1020 if (r->method == Http::METHOD_CONNECT)
1021 return true;
1022
1023 // we support OPTIONS and TRACE directed at us (with a 501 reply, for now)
1024 // we also support forwarding OPTIONS and TRACE, except for the *-URI ones
1027
1028 if (r->method == Http::METHOD_PURGE)
1029 return true;
1030
1031 /* does method match the protocol? */
1032 switch (r->url.getScheme()) {
1033
1034 case AnyP::PROTO_URN:
1035 case AnyP::PROTO_HTTP:
1036 return true;
1037
1038 case AnyP::PROTO_FTP:
1039 if (r->method == Http::METHOD_PUT ||
1040 r->method == Http::METHOD_GET ||
1042 return true;
1043 return false;
1044
1045 case AnyP::PROTO_WAIS:
1046 case AnyP::PROTO_WHOIS:
1047 if (r->method == Http::METHOD_GET ||
1049 return true;
1050 return false;
1051
1052 case AnyP::PROTO_HTTPS:
1053#if USE_OPENSSL || HAVE_LIBGNUTLS
1054 return true;
1055#else
1056 /*
1057 * Squid can't originate an SSL connection, so it should
1058 * never receive an "https:" URL. It should always be
1059 * CONNECT instead.
1060 */
1061 return false;
1062#endif
1063
1064 default:
1065 return false;
1066 }
1067
1068 /* notreached */
1069 return false;
1070}
1071
1073 scheme_(aScheme),
1074 hostIsNumeric_(false)
1075{
1076 *host_=0;
1077}
1078
1079// TODO: fix code duplication with AnyP::Uri::parse()
1080char *
1081AnyP::Uri::cleanup(const char *uri)
1082{
1083 char *cleanedUri = nullptr;
1084 switch (Config.uri_whitespace) {
1085 case URI_WHITESPACE_ALLOW: {
1087 cleanedUri = xstrndup(rfc1738_do_escape(uri, flags), MAX_URL);
1088 break;
1089 }
1090
1093 break;
1094
1095 case URI_WHITESPACE_CHOP: {
1096 const auto pos = strcspn(uri, w_space);
1097 char *choppedUri = nullptr;
1098 if (pos < strlen(uri))
1099 choppedUri = xstrndup(uri, pos + 1);
1100 cleanedUri = xstrndup(rfc1738_do_escape(choppedUri ? choppedUri : uri,
1102 cleanedUri[pos] = '\0';
1103 xfree(choppedUri);
1104 break;
1105 }
1106
1109 default: {
1110 // TODO: avoid duplication with urlParse()
1111 const char *t;
1112 char *tmp_uri = static_cast<char*>(xmalloc(strlen(uri) + 1));
1113 char *q = tmp_uri;
1114 t = uri;
1115 while (*t) {
1116 if (!xisspace(*t)) {
1117 *q = *t;
1118 ++q;
1119 }
1120 ++t;
1121 }
1122 *q = '\0';
1123 cleanedUri = xstrndup(rfc1738_escape_unescaped(tmp_uri), MAX_URL);
1124 xfree(tmp_uri);
1125 break;
1126 }
1127 }
1128
1129 assert(cleanedUri);
1130 return cleanedUri;
1131}
1132
#define Assure(condition)
Definition Assure.h:35
#define Here()
source code location of the caller
Definition Here.h:15
#define SQUIDSBUFPH
Definition SBuf.h:31
void SBufToCstring(char *d, const SBuf &s)
Definition SBuf.h:756
#define SQUIDSBUFPRINT(s)
Definition SBuf.h:32
class SquidConfig Config
constexpr bool Less(const A a, const B b)
whether integer a is less than integer b, with correct overflow handling
Definition SquidMath.h:48
int stringHasWhitespace(const char *)
Definition String.cc:294
int stringHasCntl(const char *)
Definition String.cc:301
std::ostream & CurrentException(std::ostream &os)
prints active (i.e., thrown but not yet handled) exception
bool urlCheckRequest(const HttpRequest *r)
Definition Uri.cc:1007
static const char valid_hostname_chars[]
Definition Uri.cc:28
static const char valid_hostname_chars_u[]
Definition Uri.cc:22
bool urlIsRelative(const char *url)
Definition Uri.cc:851
void urlInitialize(void)
Definition Uri.cc:217
static const CharacterSet & PathChars()
Characters which are valid within a URI path section.
Definition Uri.cc:55
int matchDomainName(const char *h, const char *d, MatchDomainNameFlags flags)
Definition Uri.cc:908
char * urlCanonicalCleanWithoutRequest(const SBuf &url, const HttpRequestMethod &method, const AnyP::UriScheme &scheme)
Definition Uri.cc:795
static AnyP::UriScheme uriParseScheme(Parser::Tokenizer &tok)
Definition Uri.cc:272
static const CharacterSet & UserInfoChars()
Characters which are valid within a URI userinfo section.
Definition Uri.cc:37
const char * urlCanonicalFakeHttps(const HttpRequest *request)
Definition Uri.cc:824
bool urlAppendDomain(char *host)
apply append_domain config to the given hostname
Definition Uri.cc:302
MatchDomainNameFlags
Definition Uri.h:238
@ mdnRejectSubsubDomains
Definition Uri.h:241
@ mdnHonorWildcards
Definition Uri.h:240
#define assert(EX)
Definition assert.h:17
static std::optional< Host > ParseIp(const Ip::Address &)
converts an already parsed IP address to a Host object
Definition Host.cc:15
static std::optional< Host > ParseSimpleDomainName(const SBuf &)
Definition Host.cc:49
static AnyP::ProtocolType FindProtocolType(const SBuf &)
Definition UriScheme.cc:52
Port defaultPort() const
Definition UriScheme.cc:71
SBuf image() const
Definition UriScheme.h:57
static const SBuf & SlashPath()
the static '/' default URL-path
Definition Uri.cc:152
SBuf parseHost(Parser::Tokenizer &) const
Definition Uri.cc:637
void parseUrn(Parser::Tokenizer &)
Definition Uri.cc:606
AnyP::UriScheme const & getScheme() const
Definition Uri.h:58
void touch()
clear the cached URI display forms
Definition Uri.cc:717
SBuf & authority(bool requirePort=false) const
Definition Uri.cc:726
static SBuf DecodeOrDupe(const SBuf &input)
Definition Uri.cc:137
void path(const char *p)
Definition Uri.h:96
char host_[SQUIDHOSTNAMELEN]
string representation of the URI authority name or IP
Definition Uri.h:188
const char * host(void) const
Definition Uri.h:76
SBuf & absolutePath() const
RFC 3986 section 4.2 relative reference called 'absolute-path'.
Definition Uri.cc:780
Uri()
Definition Uri.h:36
static std::optional< SBuf > Decode(const SBuf &)
Definition Uri.cc:105
std::optional< Host > parsedHost() const
Definition Uri.cc:186
static char * cleanup(const char *uri)
Definition Uri.cc:1081
void addRelativePath(const char *relUrl)
Definition Uri.cc:884
int parsePort(Parser::Tokenizer &) const
Definition Uri.cc:695
SBuf & absolute() const
Definition Uri.cc:748
static const SBuf & Asterisk()
the static '*' pseudo-URI
Definition Uri.cc:145
void port(const Port p)
reset authority port subcomponent
Definition Uri.h:90
const SBuf & path() const
Definition Uri.cc:205
void host(const char *src)
Definition Uri.cc:159
bool parse(const HttpRequestMethod &, const SBuf &url)
Definition Uri.cc:331
SBuf hostOrIp() const
Definition Uri.cc:175
static SBuf Encode(const SBuf &, const CharacterSet &expected)
Definition Uri.cc:76
optimized set of C chars, with quick membership test and merge support
CharacterSet complement(const char *complementLabel=nullptr) const
static const CharacterSet TCHAR
CharacterSet & rename(const char *label)
change name; handy in const declarations that use operators
static const CharacterSet DIGIT
static const CharacterSet ALPHA
static const CharacterSet HEXDIG
CharacterSet & remove(const unsigned char c)
remove a given character from the character set
int64_t getInt64(Http::HdrType id) const
HttpRequestMethod method
char * canonicalCleanUrl() const
AnyP::Uri url
the request URI
HttpHeader header
Definition Message.h:74
bool fromHost(const char *hostWithoutPort)
Definition Address.cc:910
bool prefix(SBuf &returnedToken, const CharacterSet &tokenChars, SBuf::size_type limit=SBuf::npos)
Definition Tokenizer.cc:79
const SBuf & remaining() const
the remaining unprocessed section of buffer
Definition Tokenizer.h:44
bool atEnd() const
whether the end of the buffer has been reached
Definition Tokenizer.h:41
bool skip(const SBuf &tokenToSkip)
Definition Tokenizer.cc:189
Definition Raw.h:21
Definition SBuf.h:94
const char * rawContent() const
Definition SBuf.cc:509
static const size_type npos
Definition SBuf.h:100
char at(size_type pos) const
Definition SBuf.h:253
const char * c_str()
Definition SBuf.cc:516
void reserveCapacity(size_type minCapacity)
Definition SBuf.cc:105
size_type length() const
Returns the number of bytes stored in SBuf.
Definition SBuf.h:419
SBuf & appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Definition SBuf.cc:229
size_type find(char c, size_type startPos=0) const
Definition SBuf.cc:584
bool isEmpty() const
Definition SBuf.h:435
const_iterator begin() const
Definition SBuf.h:587
SBuf & append(const SBuf &S)
Definition SBuf.cc:185
const_reverse_iterator rbegin() const
Definition SBuf.h:595
void reserveSpace(size_type minSpace)
Definition SBuf.h:444
size_t appendDomainLen
int strip_query_terms
char * appendDomain
struct SquidConfig::@90 onoff
int check_hostnames
int allow_underscore
an std::runtime_error with thrower location info
#define w_space
#define MYNAME
Definition Stream.h:219
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define URI_WHITESPACE_CHOP
Definition defines.h:127
#define URI_WHITESPACE_STRIP
Definition defines.h:124
#define URI_WHITESPACE_DENY
Definition defines.h:128
#define URI_WHITESPACE_ALLOW
Definition defines.h:125
#define URI_WHITESPACE_ENCODE
Definition defines.h:126
#define MAX_URL
Definition defines.h:76
static int port
#define MAX_IPSTRLEN
Length of buffer that needs to be allocated to old a null-terminated IP-string.
Definition forward.h:25
const char * ProtocolType_str[]
uint16_t KnownPort
validated/supported port number; these values are never zero
Definition UriScheme.h:23
@ PROTO_NONE
@ PROTO_HTTPS
@ PROTO_UNKNOWN
@ PROTO_HTTP
@ PROTO_FTP
@ PROTO_WHOIS
@ PROTO_MAX
@ PROTO_URN
@ PROTO_WAIS
@ METHOD_TRACE
Definition MethodType.h:30
@ METHOD_PUT
Definition MethodType.h:27
@ METHOD_OPTIONS
Definition MethodType.h:31
@ METHOD_CONNECT
Definition MethodType.h:29
@ METHOD_GET
Definition MethodType.h:25
@ METHOD_PURGE
Definition MethodType.h:92
@ METHOD_HEAD
Definition MethodType.h:28
#define xfree
#define xmalloc
#define RFC1738_ESCAPE_NOSPACE
Definition rfc1738.h:18
char * rfc1738_do_escape(const char *url, int flags)
Definition rfc1738.cc:56
#define RFC1738_ESCAPE_UNESCAPED
Definition rfc1738.h:21
#define rfc1738_escape_unescaped(x)
Definition rfc1738.h:55
void rfc1738_unescape(char *url)
Definition rfc1738.cc:146
#define SQUIDHOSTNAMELEN
Definition rfc2181.h:30
#define LOCAL_ARRAY(type, name, size)
Definition squid.h:62
Definition parse.c:160
#define xisspace(x)
Definition xis.h:15
#define xtolower(x)
Definition xis.h:17
char * xstrncpy(char *dst, const char *src, size_t n)
Definition xstring.cc:37
char * xstrndup(const char *s, size_t n)
Definition xstring.cc:56