]>
git.ipfire.org Git - thirdparty/squid.git/blob - src/url.cc
2 * Copyright (C) 1996-2015 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 23 URL Parsing */
13 #include "HttpRequest.h"
15 #include "SquidConfig.h"
16 #include "SquidString.h"
19 static HttpRequest
*urlParseFinish(const HttpRequestMethod
& method
,
20 const AnyP::ProtocolType protocol
,
21 const char *const urlpath
,
22 const char *const host
,
25 HttpRequest
*request
);
26 static HttpRequest
*urnParse(const HttpRequestMethod
& method
, char *urn
, HttpRequest
*request
);
27 static const char valid_hostname_chars_u
[] =
28 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
29 "abcdefghijklmnopqrstuvwxyz"
33 static const char valid_hostname_chars
[] =
34 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
35 "abcdefghijklmnopqrstuvwxyz"
43 static SBuf
star("*");
50 debugs(23, 5, "urlInitialize: Initializing...");
51 /* this ensures that the number of protocol strings is the same as
52 * the enum slots allocated because the last enum is always 'MAX'.
54 assert(strcmp(AnyP::ProtocolType_str
[AnyP::PROTO_MAX
], "MAX") == 0);
56 * These test that our matchDomainName() function works the
57 * way we expect it to.
59 assert(0 == matchDomainName("foo.com", "foo.com"));
60 assert(0 == matchDomainName(".foo.com", "foo.com"));
61 assert(0 == matchDomainName("foo.com", ".foo.com"));
62 assert(0 == matchDomainName(".foo.com", ".foo.com"));
63 assert(0 == matchDomainName("x.foo.com", ".foo.com"));
64 assert(0 != matchDomainName("x.foo.com", "foo.com"));
65 assert(0 != matchDomainName("foo.com", "x.foo.com"));
66 assert(0 != matchDomainName("bar.com", "foo.com"));
67 assert(0 != matchDomainName(".bar.com", "foo.com"));
68 assert(0 != matchDomainName(".bar.com", ".foo.com"));
69 assert(0 != matchDomainName("bar.com", ".foo.com"));
70 assert(0 < matchDomainName("zzz.com", "foo.com"));
71 assert(0 > matchDomainName("aaa.com", "foo.com"));
72 assert(0 == matchDomainName("FOO.com", "foo.COM"));
73 assert(0 < matchDomainName("bfoo.com", "afoo.com"));
74 assert(0 > matchDomainName("afoo.com", "bfoo.com"));
75 assert(0 < matchDomainName("x-foo.com", ".foo.com"));
80 * urlParseProtocol() takes begin (b) and end (e) pointers, but for
81 * backwards compatibility, e defaults to NULL, in which case we
82 * assume b is NULL-terminated.
85 urlParseProtocol(const char *b
, const char *e
)
88 * if e is NULL, b must be NULL terminated and we
89 * make e point to the first whitespace character
94 e
= b
+ strcspn(b
, ":");
98 /* test common stuff first */
100 if (strncasecmp(b
, "http", len
) == 0)
101 return AnyP::PROTO_HTTP
;
103 if (strncasecmp(b
, "ftp", len
) == 0)
104 return AnyP::PROTO_FTP
;
106 if (strncasecmp(b
, "https", len
) == 0)
107 return AnyP::PROTO_HTTPS
;
109 if (strncasecmp(b
, "file", len
) == 0)
110 return AnyP::PROTO_FTP
;
112 if (strncasecmp(b
, "coap", len
) == 0)
113 return AnyP::PROTO_COAP
;
115 if (strncasecmp(b
, "coaps", len
) == 0)
116 return AnyP::PROTO_COAPS
;
118 if (strncasecmp(b
, "gopher", len
) == 0)
119 return AnyP::PROTO_GOPHER
;
121 if (strncasecmp(b
, "wais", len
) == 0)
122 return AnyP::PROTO_WAIS
;
124 if (strncasecmp(b
, "cache_object", len
) == 0)
125 return AnyP::PROTO_CACHE_OBJECT
;
127 if (strncasecmp(b
, "urn", len
) == 0)
128 return AnyP::PROTO_URN
;
130 if (strncasecmp(b
, "whois", len
) == 0)
131 return AnyP::PROTO_WHOIS
;
133 return AnyP::PROTO_NONE
;
137 urlDefaultPort(AnyP::ProtocolType p
)
141 case AnyP::PROTO_HTTP
:
144 case AnyP::PROTO_HTTPS
:
147 case AnyP::PROTO_FTP
:
150 case AnyP::PROTO_COAP
:
151 case AnyP::PROTO_COAPS
:
152 // coaps:// default is TBA as of draft-ietf-core-coap-08.
153 // Assuming IANA policy of allocating same port for base and TLS protocol versions will occur.
156 case AnyP::PROTO_GOPHER
:
159 case AnyP::PROTO_WAIS
:
162 case AnyP::PROTO_CACHE_OBJECT
:
163 return CACHE_HTTP_PORT
;
165 case AnyP::PROTO_WHOIS
:
176 * If the 'request' arg is non-NULL, put parsed values there instead
177 * of allocating a new HttpRequest.
179 * This abuses HttpRequest as a way of representing the parsed url
180 * and its components.
181 * method is used to switch parsers and to init the HttpRequest.
182 * If method is Http::METHOD_CONNECT, then rather than a URL a hostname:port is
184 * The url is non const so that if its too long we can NULL-terminate it in place.
188 * This routine parses a URL. Its assumed that the URL is complete -
189 * ie, the end of the string is the end of the URL. Don't pass a partial
190 * URL here as this routine doesn't have any way of knowing whether
191 * its partial or not (ie, it handles the case of no trailing slash as
192 * being "end of host with implied path of /".
195 urlParse(const HttpRequestMethod
& method
, char *url
, HttpRequest
*request
)
197 LOCAL_ARRAY(char, proto
, MAX_URL
);
198 LOCAL_ARRAY(char, login
, MAX_URL
);
199 LOCAL_ARRAY(char, host
, MAX_URL
);
200 LOCAL_ARRAY(char, urlpath
, MAX_URL
);
204 AnyP::ProtocolType protocol
= AnyP::PROTO_NONE
;
209 proto
[0] = host
[0] = urlpath
[0] = login
[0] = '\0';
211 if ((l
= strlen(url
)) + Config
.appendDomainLen
> (MAX_URL
- 1)) {
212 /* terminate so it doesn't overflow other buffers */
213 *(url
+ (MAX_URL
>> 1)) = '\0';
214 debugs(23, DBG_IMPORTANT
, "urlParse: URL too large (" << l
<< " bytes)");
217 if (method
== Http::METHOD_CONNECT
) {
220 if (sscanf(url
, "[%[^]]]:%d", host
, &port
) < 1)
221 if (sscanf(url
, "%[^:]:%d", host
, &port
) < 1)
224 } else if ((method
== Http::METHOD_OPTIONS
|| method
== Http::METHOD_TRACE
) &&
225 URL::Asterisk().cmp(url
) == 0) {
226 protocol
= AnyP::PROTO_HTTP
;
227 port
= urlDefaultPort(protocol
);
228 return urlParseFinish(method
, protocol
, url
, host
, SBuf(), port
, request
);
229 } else if (!strncmp(url
, "urn:", 4)) {
230 return urnParse(method
, url
, request
);
235 /* Find first : - everything before is protocol */
236 for (i
= 0, dst
= proto
; i
< l
&& *src
!= ':'; ++i
, ++src
, ++dst
) {
244 if ((i
+3) > l
|| *src
!= ':' || *(src
+ 1) != '/' || *(src
+ 2) != '/')
249 /* Then everything until first /; thats host (and port; which we'll look for here later) */
250 // bug 1881: If we don't get a "/" then we imply it was there
251 // bug 3074: We could just be given a "?" or "#". These also imply "/"
252 // bug 3233: whitespace is also a hostname delimiter.
253 for (dst
= host
; i
< l
&& *src
!= '/' && *src
!= '?' && *src
!= '#' && *src
!= '\0' && !xisspace(*src
); ++i
, ++src
, ++dst
) {
258 * We can't check for "i >= l" here because we could be at the end of the line
259 * and have a perfectly valid URL w/ no trailing '/'. In this case we assume we've
260 * been -given- a valid URL and the path is just '/'.
266 // bug 3074: received 'path' starting with '?', '#', or '\0' implies '/'
267 if (*src
== '?' || *src
== '#' || *src
== '\0') {
273 /* Then everything from / (inclusive) until \r\n or \0 - thats urlpath */
274 for (; i
< l
&& *src
!= '\r' && *src
!= '\n' && *src
!= '\0'; ++i
, ++src
, ++dst
) {
278 /* We -could- be at the end of the buffer here */
281 /* If the URL path is empty we set it to be "/" */
282 if (dst
== urlpath
) {
288 protocol
= urlParseProtocol(proto
);
289 port
= urlDefaultPort(protocol
);
291 /* Is there any login information? (we should eventually parse it above) */
292 t
= strrchr(host
, '@');
294 strncpy((char *) login
, (char *) host
, sizeof(login
)-1);
295 login
[sizeof(login
)-1] = '\0';
296 t
= strrchr(login
, '@');
298 strncpy((char *) host
, t
+ 1, sizeof(host
)-1);
299 host
[sizeof(host
)-1] = '\0';
302 /* Is there any host information? (we should eventually parse it above) */
304 /* strip any IPA brackets. valid under IPv6. */
306 /* only for IPv6 sadly, pre-IPv6/URL code can't handle the clean result properly anyway. */
311 for (; i
< l
&& *src
!= ']' && *src
!= '\0'; ++i
, ++src
, ++dst
) {
315 /* we moved in-place, so truncate the actual hostname found */
319 /* skip ahead to either start of port, or original EOS */
320 while (*dst
!= '\0' && *dst
!= ':')
324 t
= strrchr(host
, ':');
326 if (t
!= strchr(host
,':') ) {
327 /* RFC 2732 states IPv6 "SHOULD" be bracketed. allowing for times when its not. */
328 /* RFC 3986 'update' simply modifies this to an "is" with no emphasis at all! */
329 /* therefore we MUST accept the case where they are not bracketed at all. */
334 // Bug 3183 sanity check: If scheme is present, host must be too.
335 if (protocol
!= AnyP::PROTO_NONE
&& host
[0] == '\0') {
336 debugs(23, DBG_IMPORTANT
, "SECURITY ALERT: Missing hostname in URL '" << url
<< "'. see access.log for details.");
340 if (t
&& *t
== ':') {
347 for (t
= host
; *t
; ++t
)
350 if (stringHasWhitespace(host
)) {
351 if (URI_WHITESPACE_STRIP
== Config
.uri_whitespace
) {
364 debugs(23, 3, "urlParse: Split URL '" << url
<< "' into proto='" << proto
<< "', host='" << host
<< "', port='" << port
<< "', path='" << urlpath
<< "'");
366 if (Config
.onoff
.check_hostnames
&& strspn(host
, Config
.onoff
.allow_underscore
? valid_hostname_chars_u
: valid_hostname_chars
) != strlen(host
)) {
367 debugs(23, DBG_IMPORTANT
, "urlParse: Illegal character in hostname '" << host
<< "'");
371 /* For IPV6 addresses also check for a colon */
372 if (Config
.appendDomain
&& !strchr(host
, '.') && !strchr(host
, ':'))
373 strncat(host
, Config
.appendDomain
, SQUIDHOSTNAMELEN
- strlen(host
) - 1);
375 /* remove trailing dots from hostnames */
376 while ((l
= strlen(host
)) > 0 && host
[--l
] == '.')
379 /* reject duplicate or leading dots */
380 if (strstr(host
, "..") || *host
== '.') {
381 debugs(23, DBG_IMPORTANT
, "urlParse: Illegal hostname '" << host
<< "'");
385 if (port
< 1 || port
> 65535) {
386 debugs(23, 3, "urlParse: Invalid port '" << port
<< "'");
390 #if HARDCODE_DENY_PORTS
391 /* These ports are filtered in the default squid.conf, but
392 * maybe someone wants them hardcoded... */
393 if (port
== 7 || port
== 9 || port
== 19) {
394 debugs(23, DBG_CRITICAL
, "urlParse: Deny access to port " << port
);
399 if (stringHasWhitespace(urlpath
)) {
400 debugs(23, 2, "urlParse: URI has whitespace: {" << url
<< "}");
402 switch (Config
.uri_whitespace
) {
404 case URI_WHITESPACE_DENY
:
407 case URI_WHITESPACE_ALLOW
:
410 case URI_WHITESPACE_ENCODE
:
411 t
= rfc1738_escape_unescaped(urlpath
);
412 xstrncpy(urlpath
, t
, MAX_URL
);
415 case URI_WHITESPACE_CHOP
:
416 *(urlpath
+ strcspn(urlpath
, w_space
)) = '\0';
419 case URI_WHITESPACE_STRIP
:
433 return urlParseFinish(method
, protocol
, urlpath
, host
, SBuf(login
), port
, request
);
437 * Update request with parsed URI data. If the request arg is
438 * non-NULL, put parsed values there instead of allocating a new
442 urlParseFinish(const HttpRequestMethod
& method
,
443 const AnyP::ProtocolType protocol
,
444 const char *const urlpath
,
445 const char *const host
,
448 HttpRequest
*request
)
451 request
= new HttpRequest(method
, protocol
, urlpath
);
453 request
->initHTTP(method
, protocol
, urlpath
);
454 safe_free(request
->canonical
);
457 request
->SetHost(host
);
458 request
->url
.userInfo(login
);
459 request
->port
= (unsigned short) port
;
464 urnParse(const HttpRequestMethod
& method
, char *urn
, HttpRequest
*request
)
466 debugs(50, 5, "urnParse: " << urn
);
468 request
->initHTTP(method
, AnyP::PROTO_URN
, urn
+ 4);
469 safe_free(request
->canonical
);
473 return new HttpRequest(method
, AnyP::PROTO_URN
, urn
+ 4);
477 urlCanonical(HttpRequest
* request
)
479 LOCAL_ARRAY(char, portbuf
, 32);
480 LOCAL_ARRAY(char, urlbuf
, MAX_URL
);
482 if (request
->canonical
)
483 return request
->canonical
;
485 if (request
->url
.getScheme() == AnyP::PROTO_URN
) {
486 snprintf(urlbuf
, MAX_URL
, "urn:" SQUIDSTRINGPH
,
487 SQUIDSTRINGPRINT(request
->urlpath
));
489 switch (request
->method
.id()) {
491 case Http::METHOD_CONNECT
:
492 snprintf(urlbuf
, MAX_URL
, "%s:%d", request
->GetHost(), request
->port
);
498 if (request
->port
!= urlDefaultPort(request
->url
.getScheme()))
499 snprintf(portbuf
, 32, ":%d", request
->port
);
501 snprintf(urlbuf
, MAX_URL
, "%s://" SQUIDSBUFPH
"%s%s%s" SQUIDSTRINGPH
,
502 request
->url
.getScheme().c_str(),
503 SQUIDSBUFPRINT(request
->url
.userInfo()),
504 !request
->url
.userInfo().isEmpty() ? "@" : "",
507 SQUIDSTRINGPRINT(request
->urlpath
));
512 return (request
->canonical
= xstrdup(urlbuf
));
515 /** \todo AYJ: Performance: This is an *almost* duplicate of urlCanonical. But elides the query-string.
516 * After copying it on in the first place! Would be less code to merge the two with a flag parameter.
517 * and never copy the query-string part in the first place
520 urlCanonicalClean(const HttpRequest
* request
)
522 LOCAL_ARRAY(char, buf
, MAX_URL
);
523 LOCAL_ARRAY(char, portbuf
, 32);
526 if (request
->url
.getScheme() == AnyP::PROTO_URN
) {
527 snprintf(buf
, MAX_URL
, "urn:" SQUIDSTRINGPH
,
528 SQUIDSTRINGPRINT(request
->urlpath
));
530 switch (request
->method
.id()) {
532 case Http::METHOD_CONNECT
:
533 snprintf(buf
, MAX_URL
, "%s:%d", request
->GetHost(), request
->port
);
539 if (request
->port
!= urlDefaultPort(request
->url
.getScheme()))
540 snprintf(portbuf
, 32, ":%d", request
->port
);
542 snprintf(buf
, MAX_URL
, "%s://" SQUIDSBUFPH
"%s%s%s" SQUIDSTRINGPH
,
543 request
->url
.getScheme().c_str(),
544 SQUIDSBUFPRINT(request
->url
.userInfo()),
545 (request
->url
.userInfo().isEmpty() ? "" : "@"),
548 SQUIDSTRINGPRINT(request
->urlpath
));
550 // strip arguments AFTER a question-mark
551 if (Config
.onoff
.strip_query_terms
)
552 if ((t
= strchr(buf
, '?')))
558 if (stringHasCntl(buf
))
559 xstrncpy(buf
, rfc1738_escape_unescaped(buf
), MAX_URL
);
565 * Yet another alternative to urlCanonical.
566 * This one adds the https:// parts to Http::METHOD_CONNECT URL
567 * for use in error page outputs.
568 * Luckily we can leverage the others instead of duplicating.
571 urlCanonicalFakeHttps(const HttpRequest
* request
)
573 LOCAL_ARRAY(char, buf
, MAX_URL
);
575 // method CONNECT and port HTTPS
576 if (request
->method
== Http::METHOD_CONNECT
&& request
->port
== 443) {
577 snprintf(buf
, MAX_URL
, "https://%s/*", request
->GetHost());
581 // else do the normal complete canonical thing.
582 return urlCanonicalClean(request
);
586 * Test if a URL is relative.
588 * RFC 2396, Section 5 (Page 17) implies that in a relative URL, a '/' will
589 * appear before a ':'.
592 urlIsRelative(const char *url
)
603 for (p
= url
; *p
!= '\0' && *p
!= ':' && *p
!= '/'; ++p
);
612 * Convert a relative URL to an absolute URL using the context of a given
615 * It is assumed that you have already ensured that the URL is relative.
617 * If NULL is returned it is an indication that the method in use in the
618 * request does not distinguish between relative and absolute and you should
619 * use the url unchanged.
621 * If non-NULL is returned, it is up to the caller to free the resulting
622 * memory using safe_free().
625 urlMakeAbsolute(const HttpRequest
* req
, const char *relUrl
)
628 if (req
->method
.id() == Http::METHOD_CONNECT
) {
632 char *urlbuf
= (char *)xmalloc(MAX_URL
* sizeof(char));
634 if (req
->url
.getScheme() == AnyP::PROTO_URN
) {
635 snprintf(urlbuf
, MAX_URL
, "urn:" SQUIDSTRINGPH
,
636 SQUIDSTRINGPRINT(req
->urlpath
));
642 if (req
->port
!= urlDefaultPort(req
->url
.getScheme())) {
643 urllen
= snprintf(urlbuf
, MAX_URL
, "%s://" SQUIDSBUFPH
"%s%s:%d",
644 req
->url
.getScheme().c_str(),
645 SQUIDSBUFPRINT(req
->url
.userInfo()),
646 !req
->url
.userInfo().isEmpty() ? "@" : "",
651 urllen
= snprintf(urlbuf
, MAX_URL
, "%s://" SQUIDSBUFPH
"%s%s",
652 req
->url
.getScheme().c_str(),
653 SQUIDSBUFPRINT(req
->url
.userInfo()),
654 !req
->url
.userInfo().isEmpty() ? "@" : "",
659 if (relUrl
[0] == '/') {
660 strncpy(&urlbuf
[urllen
], relUrl
, MAX_URL
- urllen
- 1);
662 const char *path
= req
->urlpath
.termedBuf();
663 const char *last_slash
= strrchr(path
, '/');
665 if (last_slash
== NULL
) {
666 urlbuf
[urllen
] = '/';
668 strncpy(&urlbuf
[urllen
], relUrl
, MAX_URL
- urllen
- 1);
671 size_t pathlen
= last_slash
- path
;
672 if (pathlen
> MAX_URL
- urllen
- 1) {
673 pathlen
= MAX_URL
- urllen
- 1;
675 strncpy(&urlbuf
[urllen
], path
, pathlen
);
677 if (urllen
+ 1 < MAX_URL
) {
678 strncpy(&urlbuf
[urllen
], relUrl
, MAX_URL
- urllen
- 1);
687 matchDomainName(const char *h
, const char *d
, bool honorWildcards
)
700 * Start at the ends of the two strings and work towards the
703 while (xtolower(h
[--hl
]) == xtolower(d
[--dl
])) {
704 if (hl
== 0 && dl
== 0) {
706 * We made it all the way to the beginning of both
707 * strings without finding any difference.
714 * The host string is shorter than the domain string.
715 * There is only one case when this can be a match.
716 * If the domain is just one character longer, and if
717 * that character is a leading '.' then we call it a
721 if (1 == dl
&& '.' == d
[0])
729 * The domain string is shorter than the host string.
730 * This is a match only if the first domain character
742 * We found different characters in the same position (from the end).
745 // If the h has a form of "*.foo.com" and d has a form of "x.foo.com"
746 // then the h[hl] points to '*', h[hl+1] to '.' and d[dl] to 'x'
747 // The following checks are safe, the "h[hl + 1]" in the worst case is '\0'.
748 if (honorWildcards
&& h
[hl
] == '*' && h
[hl
+ 1] == '.')
752 * If one of those character is '.' then its special. In order
753 * for splay tree sorting to work properly, "x-foo.com" must
754 * be greater than ".foo.com" even though '-' is less than '.'.
762 return (xtolower(h
[hl
]) - xtolower(d
[dl
]));
766 * return true if we can serve requests for this method.
769 urlCheckRequest(const HttpRequest
* r
)
772 /* protocol "independent" methods
774 * actually these methods are specific to HTTP:
775 * they are methods we recieve on our HTTP port,
776 * and if we had a FTP listener would not be relevant
779 * So, we should delegate them to HTTP. The problem is that we
780 * do not have a default protocol from the client side of HTTP.
783 if (r
->method
== Http::METHOD_CONNECT
)
786 // we support OPTIONS and TRACE directed at us (with a 501 reply, for now)
787 // we also support forwarding OPTIONS and TRACE, except for the *-URI ones
788 if (r
->method
== Http::METHOD_OPTIONS
|| r
->method
== Http::METHOD_TRACE
)
789 return (r
->header
.getInt64(HDR_MAX_FORWARDS
) == 0 || URL::Asterisk().cmp(r
->urlpath
.rawBuf(), r
->urlpath
.size()) != 0);
791 if (r
->method
== Http::METHOD_PURGE
)
794 /* does method match the protocol? */
795 switch (r
->url
.getScheme()) {
797 case AnyP::PROTO_URN
:
799 case AnyP::PROTO_HTTP
:
801 case AnyP::PROTO_CACHE_OBJECT
:
805 case AnyP::PROTO_FTP
:
807 if (r
->method
== Http::METHOD_PUT
)
810 case AnyP::PROTO_GOPHER
:
812 case AnyP::PROTO_WAIS
:
814 case AnyP::PROTO_WHOIS
:
815 if (r
->method
== Http::METHOD_GET
)
817 else if (r
->method
== Http::METHOD_HEAD
)
822 case AnyP::PROTO_HTTPS
:
831 * Squid can't originate an SSL connection, so it should
832 * never receive an "https:" URL. It should always be
847 * Quick-n-dirty host extraction from a URL. Steps:
849 * Skip any '/' after the colon
850 * Copy the next SQUID_MAXHOSTNAMELEN bytes to host[]
851 * Look for an ending '/' or ':' and terminate
852 * Look for login info preceeded by '@'
859 char * extract(char const *url
);
862 static char Host
[SQUIDHOSTNAMELEN
];
863 void init(char const *);
864 void findHostStart();
865 void trimTrailingChars();
867 char const *hostStart
;
872 urlHostname(const char *url
)
874 return URLHostName().extract(url
);
877 char URLHostName::Host
[SQUIDHOSTNAMELEN
];
880 URLHostName::init(char const *aUrl
)
887 URLHostName::findHostStart()
889 if (NULL
== (hostStart
= strchr(url
, ':')))
894 while (*hostStart
!= '\0' && *hostStart
== '/')
897 if (*hostStart
== ']')
902 URLHostName::trimTrailingChars()
906 if ((t
= strchr(Host
, '/')))
909 if ((t
= strrchr(Host
, ':')))
912 if ((t
= strchr(Host
, ']')))
917 URLHostName::trimAuth()
921 if ((t
= strrchr(Host
, '@'))) {
923 memmove(Host
, t
, strlen(t
) + 1);
928 URLHostName::extract(char const *aUrl
)
933 if (hostStart
== NULL
)
936 xstrncpy(Host
, hostStart
, SQUIDHOSTNAMELEN
);