netwerk/base/src/nsURLHelper.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim:set ts=4 sw=4 sts=4 et cindent: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "mozilla/RangedPtr.h"
michael@0 8
michael@0 9 #include "nsURLHelper.h"
michael@0 10 #include "nsIFile.h"
michael@0 11 #include "nsIURLParser.h"
michael@0 12 #include "nsCOMPtr.h"
michael@0 13 #include "nsCRT.h"
michael@0 14 #include "nsNetCID.h"
michael@0 15 #include "prnetdb.h"
michael@0 16
michael@0 17 using namespace mozilla;
michael@0 18
michael@0 19 //----------------------------------------------------------------------------
michael@0 20 // Init/Shutdown
michael@0 21 //----------------------------------------------------------------------------
michael@0 22
michael@0 23 static bool gInitialized = false;
michael@0 24 static nsIURLParser *gNoAuthURLParser = nullptr;
michael@0 25 static nsIURLParser *gAuthURLParser = nullptr;
michael@0 26 static nsIURLParser *gStdURLParser = nullptr;
michael@0 27
michael@0 28 static void
michael@0 29 InitGlobals()
michael@0 30 {
michael@0 31 nsCOMPtr<nsIURLParser> parser;
michael@0 32
michael@0 33 parser = do_GetService(NS_NOAUTHURLPARSER_CONTRACTID);
michael@0 34 NS_ASSERTION(parser, "failed getting 'noauth' url parser");
michael@0 35 if (parser) {
michael@0 36 gNoAuthURLParser = parser.get();
michael@0 37 NS_ADDREF(gNoAuthURLParser);
michael@0 38 }
michael@0 39
michael@0 40 parser = do_GetService(NS_AUTHURLPARSER_CONTRACTID);
michael@0 41 NS_ASSERTION(parser, "failed getting 'auth' url parser");
michael@0 42 if (parser) {
michael@0 43 gAuthURLParser = parser.get();
michael@0 44 NS_ADDREF(gAuthURLParser);
michael@0 45 }
michael@0 46
michael@0 47 parser = do_GetService(NS_STDURLPARSER_CONTRACTID);
michael@0 48 NS_ASSERTION(parser, "failed getting 'std' url parser");
michael@0 49 if (parser) {
michael@0 50 gStdURLParser = parser.get();
michael@0 51 NS_ADDREF(gStdURLParser);
michael@0 52 }
michael@0 53
michael@0 54 gInitialized = true;
michael@0 55 }
michael@0 56
michael@0 57 void
michael@0 58 net_ShutdownURLHelper()
michael@0 59 {
michael@0 60 if (gInitialized) {
michael@0 61 NS_IF_RELEASE(gNoAuthURLParser);
michael@0 62 NS_IF_RELEASE(gAuthURLParser);
michael@0 63 NS_IF_RELEASE(gStdURLParser);
michael@0 64 gInitialized = false;
michael@0 65 }
michael@0 66 }
michael@0 67
michael@0 68 //----------------------------------------------------------------------------
michael@0 69 // nsIURLParser getters
michael@0 70 //----------------------------------------------------------------------------
michael@0 71
michael@0 72 nsIURLParser *
michael@0 73 net_GetAuthURLParser()
michael@0 74 {
michael@0 75 if (!gInitialized)
michael@0 76 InitGlobals();
michael@0 77 return gAuthURLParser;
michael@0 78 }
michael@0 79
michael@0 80 nsIURLParser *
michael@0 81 net_GetNoAuthURLParser()
michael@0 82 {
michael@0 83 if (!gInitialized)
michael@0 84 InitGlobals();
michael@0 85 return gNoAuthURLParser;
michael@0 86 }
michael@0 87
michael@0 88 nsIURLParser *
michael@0 89 net_GetStdURLParser()
michael@0 90 {
michael@0 91 if (!gInitialized)
michael@0 92 InitGlobals();
michael@0 93 return gStdURLParser;
michael@0 94 }
michael@0 95
michael@0 96 //---------------------------------------------------------------------------
michael@0 97 // GetFileFromURLSpec implementations
michael@0 98 //---------------------------------------------------------------------------
michael@0 99 nsresult
michael@0 100 net_GetURLSpecFromDir(nsIFile *aFile, nsACString &result)
michael@0 101 {
michael@0 102 nsAutoCString escPath;
michael@0 103 nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
michael@0 104 if (NS_FAILED(rv))
michael@0 105 return rv;
michael@0 106
michael@0 107 if (escPath.Last() != '/') {
michael@0 108 escPath += '/';
michael@0 109 }
michael@0 110
michael@0 111 result = escPath;
michael@0 112 return NS_OK;
michael@0 113 }
michael@0 114
michael@0 115 nsresult
michael@0 116 net_GetURLSpecFromFile(nsIFile *aFile, nsACString &result)
michael@0 117 {
michael@0 118 nsAutoCString escPath;
michael@0 119 nsresult rv = net_GetURLSpecFromActualFile(aFile, escPath);
michael@0 120 if (NS_FAILED(rv))
michael@0 121 return rv;
michael@0 122
michael@0 123 // if this file references a directory, then we need to ensure that the
michael@0 124 // URL ends with a slash. this is important since it affects the rules
michael@0 125 // for relative URL resolution when this URL is used as a base URL.
michael@0 126 // if the file does not exist, then we make no assumption about its type,
michael@0 127 // and simply leave the URL unmodified.
michael@0 128 if (escPath.Last() != '/') {
michael@0 129 bool dir;
michael@0 130 rv = aFile->IsDirectory(&dir);
michael@0 131 if (NS_SUCCEEDED(rv) && dir)
michael@0 132 escPath += '/';
michael@0 133 }
michael@0 134
michael@0 135 result = escPath;
michael@0 136 return NS_OK;
michael@0 137 }
michael@0 138
michael@0 139 //----------------------------------------------------------------------------
michael@0 140 // file:// URL parsing
michael@0 141 //----------------------------------------------------------------------------
michael@0 142
michael@0 143 nsresult
michael@0 144 net_ParseFileURL(const nsACString &inURL,
michael@0 145 nsACString &outDirectory,
michael@0 146 nsACString &outFileBaseName,
michael@0 147 nsACString &outFileExtension)
michael@0 148 {
michael@0 149 nsresult rv;
michael@0 150
michael@0 151 outDirectory.Truncate();
michael@0 152 outFileBaseName.Truncate();
michael@0 153 outFileExtension.Truncate();
michael@0 154
michael@0 155 const nsPromiseFlatCString &flatURL = PromiseFlatCString(inURL);
michael@0 156 const char *url = flatURL.get();
michael@0 157
michael@0 158 uint32_t schemeBeg, schemeEnd;
michael@0 159 rv = net_ExtractURLScheme(flatURL, &schemeBeg, &schemeEnd, nullptr);
michael@0 160 if (NS_FAILED(rv)) return rv;
michael@0 161
michael@0 162 if (strncmp(url + schemeBeg, "file", schemeEnd - schemeBeg) != 0) {
michael@0 163 NS_ERROR("must be a file:// url");
michael@0 164 return NS_ERROR_UNEXPECTED;
michael@0 165 }
michael@0 166
michael@0 167 nsIURLParser *parser = net_GetNoAuthURLParser();
michael@0 168 NS_ENSURE_TRUE(parser, NS_ERROR_UNEXPECTED);
michael@0 169
michael@0 170 uint32_t pathPos, filepathPos, directoryPos, basenamePos, extensionPos;
michael@0 171 int32_t pathLen, filepathLen, directoryLen, basenameLen, extensionLen;
michael@0 172
michael@0 173 // invoke the parser to extract the URL path
michael@0 174 rv = parser->ParseURL(url, flatURL.Length(),
michael@0 175 nullptr, nullptr, // don't care about scheme
michael@0 176 nullptr, nullptr, // don't care about authority
michael@0 177 &pathPos, &pathLen);
michael@0 178 if (NS_FAILED(rv)) return rv;
michael@0 179
michael@0 180 // invoke the parser to extract filepath from the path
michael@0 181 rv = parser->ParsePath(url + pathPos, pathLen,
michael@0 182 &filepathPos, &filepathLen,
michael@0 183 nullptr, nullptr, // don't care about query
michael@0 184 nullptr, nullptr); // don't care about ref
michael@0 185 if (NS_FAILED(rv)) return rv;
michael@0 186
michael@0 187 filepathPos += pathPos;
michael@0 188
michael@0 189 // invoke the parser to extract the directory and filename from filepath
michael@0 190 rv = parser->ParseFilePath(url + filepathPos, filepathLen,
michael@0 191 &directoryPos, &directoryLen,
michael@0 192 &basenamePos, &basenameLen,
michael@0 193 &extensionPos, &extensionLen);
michael@0 194 if (NS_FAILED(rv)) return rv;
michael@0 195
michael@0 196 if (directoryLen > 0)
michael@0 197 outDirectory = Substring(inURL, filepathPos + directoryPos, directoryLen);
michael@0 198 if (basenameLen > 0)
michael@0 199 outFileBaseName = Substring(inURL, filepathPos + basenamePos, basenameLen);
michael@0 200 if (extensionLen > 0)
michael@0 201 outFileExtension = Substring(inURL, filepathPos + extensionPos, extensionLen);
michael@0 202 // since we are using a no-auth url parser, there will never be a host
michael@0 203 // XXX not strictly true... file://localhost/foo/bar.html is a valid URL
michael@0 204
michael@0 205 return NS_OK;
michael@0 206 }
michael@0 207
michael@0 208 //----------------------------------------------------------------------------
michael@0 209 // path manipulation functions
michael@0 210 //----------------------------------------------------------------------------
michael@0 211
michael@0 212 // Replace all /./ with a / while resolving URLs
michael@0 213 // But only till #?
michael@0 214 void
michael@0 215 net_CoalesceDirs(netCoalesceFlags flags, char* path)
michael@0 216 {
michael@0 217 /* Stolen from the old netlib's mkparse.c.
michael@0 218 *
michael@0 219 * modifies a url of the form /foo/../foo1 -> /foo1
michael@0 220 * and /foo/./foo1 -> /foo/foo1
michael@0 221 * and /foo/foo1/.. -> /foo/
michael@0 222 */
michael@0 223 char *fwdPtr = path;
michael@0 224 char *urlPtr = path;
michael@0 225 char *lastslash = path;
michael@0 226 uint32_t traversal = 0;
michael@0 227 uint32_t special_ftp_len = 0;
michael@0 228
michael@0 229 /* Remember if this url is a special ftp one: */
michael@0 230 if (flags & NET_COALESCE_DOUBLE_SLASH_IS_ROOT)
michael@0 231 {
michael@0 232 /* some schemes (for example ftp) have the speciality that
michael@0 233 the path can begin // or /%2F to mark the root of the
michael@0 234 servers filesystem, a simple / only marks the root relative
michael@0 235 to the user loging in. We remember the length of the marker */
michael@0 236 if (nsCRT::strncasecmp(path,"/%2F",4) == 0)
michael@0 237 special_ftp_len = 4;
michael@0 238 else if (nsCRT::strncmp(path,"//",2) == 0 )
michael@0 239 special_ftp_len = 2;
michael@0 240 }
michael@0 241
michael@0 242 /* find the last slash before # or ? */
michael@0 243 for(; (*fwdPtr != '\0') &&
michael@0 244 (*fwdPtr != '?') &&
michael@0 245 (*fwdPtr != '#'); ++fwdPtr)
michael@0 246 {
michael@0 247 }
michael@0 248
michael@0 249 /* found nothing, but go back one only */
michael@0 250 /* if there is something to go back to */
michael@0 251 if (fwdPtr != path && *fwdPtr == '\0')
michael@0 252 {
michael@0 253 --fwdPtr;
michael@0 254 }
michael@0 255
michael@0 256 /* search the slash */
michael@0 257 for(; (fwdPtr != path) &&
michael@0 258 (*fwdPtr != '/'); --fwdPtr)
michael@0 259 {
michael@0 260 }
michael@0 261 lastslash = fwdPtr;
michael@0 262 fwdPtr = path;
michael@0 263
michael@0 264 /* replace all %2E or %2e with . in the path */
michael@0 265 /* but stop at lastchar if non null */
michael@0 266 for(; (*fwdPtr != '\0') &&
michael@0 267 (*fwdPtr != '?') &&
michael@0 268 (*fwdPtr != '#') &&
michael@0 269 (*lastslash == '\0' || fwdPtr != lastslash); ++fwdPtr)
michael@0 270 {
michael@0 271 if (*fwdPtr == '%' && *(fwdPtr+1) == '2' &&
michael@0 272 (*(fwdPtr+2) == 'E' || *(fwdPtr+2) == 'e'))
michael@0 273 {
michael@0 274 *urlPtr++ = '.';
michael@0 275 ++fwdPtr;
michael@0 276 ++fwdPtr;
michael@0 277 }
michael@0 278 else
michael@0 279 {
michael@0 280 *urlPtr++ = *fwdPtr;
michael@0 281 }
michael@0 282 }
michael@0 283 // Copy remaining stuff past the #?;
michael@0 284 for (; *fwdPtr != '\0'; ++fwdPtr)
michael@0 285 {
michael@0 286 *urlPtr++ = *fwdPtr;
michael@0 287 }
michael@0 288 *urlPtr = '\0'; // terminate the url
michael@0 289
michael@0 290 // start again, this time for real
michael@0 291 fwdPtr = path;
michael@0 292 urlPtr = path;
michael@0 293
michael@0 294 for(; (*fwdPtr != '\0') &&
michael@0 295 (*fwdPtr != '?') &&
michael@0 296 (*fwdPtr != '#'); ++fwdPtr)
michael@0 297 {
michael@0 298 if (*fwdPtr == '/' && *(fwdPtr+1) == '.' && *(fwdPtr+2) == '/' )
michael@0 299 {
michael@0 300 // remove . followed by slash
michael@0 301 ++fwdPtr;
michael@0 302 }
michael@0 303 else if(*fwdPtr == '/' && *(fwdPtr+1) == '.' && *(fwdPtr+2) == '.' &&
michael@0 304 (*(fwdPtr+3) == '/' ||
michael@0 305 *(fwdPtr+3) == '\0' || // This will take care of
michael@0 306 *(fwdPtr+3) == '?' || // something like foo/bar/..#sometag
michael@0 307 *(fwdPtr+3) == '#'))
michael@0 308 {
michael@0 309 // remove foo/..
michael@0 310 // reverse the urlPtr to the previous slash if possible
michael@0 311 // if url does not allow relative root then drop .. above root
michael@0 312 // otherwise retain them in the path
michael@0 313 if(traversal > 0 || !(flags &
michael@0 314 NET_COALESCE_ALLOW_RELATIVE_ROOT))
michael@0 315 {
michael@0 316 if (urlPtr != path)
michael@0 317 urlPtr--; // we must be going back at least by one
michael@0 318 for(;*urlPtr != '/' && urlPtr != path; urlPtr--)
michael@0 319 ; // null body
michael@0 320 --traversal; // count back
michael@0 321 // forward the fwdPtr past the ../
michael@0 322 fwdPtr += 2;
michael@0 323 // if we have reached the beginning of the path
michael@0 324 // while searching for the previous / and we remember
michael@0 325 // that it is an url that begins with /%2F then
michael@0 326 // advance urlPtr again by 3 chars because /%2F already
michael@0 327 // marks the root of the path
michael@0 328 if (urlPtr == path && special_ftp_len > 3)
michael@0 329 {
michael@0 330 ++urlPtr;
michael@0 331 ++urlPtr;
michael@0 332 ++urlPtr;
michael@0 333 }
michael@0 334 // special case if we have reached the end
michael@0 335 // to preserve the last /
michael@0 336 if (*fwdPtr == '.' && *(fwdPtr+1) == '\0')
michael@0 337 ++urlPtr;
michael@0 338 }
michael@0 339 else
michael@0 340 {
michael@0 341 // there are to much /.. in this path, just copy them instead.
michael@0 342 // forward the urlPtr past the /.. and copying it
michael@0 343
michael@0 344 // However if we remember it is an url that starts with
michael@0 345 // /%2F and urlPtr just points at the "F" of "/%2F" then do
michael@0 346 // not overwrite it with the /, just copy .. and move forward
michael@0 347 // urlPtr.
michael@0 348 if (special_ftp_len > 3 && urlPtr == path+special_ftp_len-1)
michael@0 349 ++urlPtr;
michael@0 350 else
michael@0 351 *urlPtr++ = *fwdPtr;
michael@0 352 ++fwdPtr;
michael@0 353 *urlPtr++ = *fwdPtr;
michael@0 354 ++fwdPtr;
michael@0 355 *urlPtr++ = *fwdPtr;
michael@0 356 }
michael@0 357 }
michael@0 358 else
michael@0 359 {
michael@0 360 // count the hierachie, but only if we do not have reached
michael@0 361 // the root of some special urls with a special root marker
michael@0 362 if (*fwdPtr == '/' && *(fwdPtr+1) != '.' &&
michael@0 363 (special_ftp_len != 2 || *(fwdPtr+1) != '/'))
michael@0 364 traversal++;
michael@0 365 // copy the url incrementaly
michael@0 366 *urlPtr++ = *fwdPtr;
michael@0 367 }
michael@0 368 }
michael@0 369
michael@0 370 /*
michael@0 371 * Now lets remove trailing . case
michael@0 372 * /foo/foo1/. -> /foo/foo1/
michael@0 373 */
michael@0 374
michael@0 375 if ((urlPtr > (path+1)) && (*(urlPtr-1) == '.') && (*(urlPtr-2) == '/'))
michael@0 376 urlPtr--;
michael@0 377
michael@0 378 // Copy remaining stuff past the #?;
michael@0 379 for (; *fwdPtr != '\0'; ++fwdPtr)
michael@0 380 {
michael@0 381 *urlPtr++ = *fwdPtr;
michael@0 382 }
michael@0 383 *urlPtr = '\0'; // terminate the url
michael@0 384 }
michael@0 385
michael@0 386 nsresult
michael@0 387 net_ResolveRelativePath(const nsACString &relativePath,
michael@0 388 const nsACString &basePath,
michael@0 389 nsACString &result)
michael@0 390 {
michael@0 391 nsAutoCString name;
michael@0 392 nsAutoCString path(basePath);
michael@0 393 bool needsDelim = false;
michael@0 394
michael@0 395 if ( !path.IsEmpty() ) {
michael@0 396 char16_t last = path.Last();
michael@0 397 needsDelim = !(last == '/');
michael@0 398 }
michael@0 399
michael@0 400 nsACString::const_iterator beg, end;
michael@0 401 relativePath.BeginReading(beg);
michael@0 402 relativePath.EndReading(end);
michael@0 403
michael@0 404 bool stop = false;
michael@0 405 char c;
michael@0 406 for (; !stop; ++beg) {
michael@0 407 c = (beg == end) ? '\0' : *beg;
michael@0 408 //printf("%c [name=%s] [path=%s]\n", c, name.get(), path.get());
michael@0 409 switch (c) {
michael@0 410 case '\0':
michael@0 411 case '#':
michael@0 412 case '?':
michael@0 413 stop = true;
michael@0 414 // fall through...
michael@0 415 case '/':
michael@0 416 // delimiter found
michael@0 417 if (name.EqualsLiteral("..")) {
michael@0 418 // pop path
michael@0 419 // If we already have the delim at end, then
michael@0 420 // skip over that when searching for next one to the left
michael@0 421 int32_t offset = path.Length() - (needsDelim ? 1 : 2);
michael@0 422 // First check for errors
michael@0 423 if (offset < 0 )
michael@0 424 return NS_ERROR_MALFORMED_URI;
michael@0 425 int32_t pos = path.RFind("/", false, offset);
michael@0 426 if (pos >= 0)
michael@0 427 path.Truncate(pos + 1);
michael@0 428 else
michael@0 429 path.Truncate();
michael@0 430 }
michael@0 431 else if (name.IsEmpty() || name.EqualsLiteral(".")) {
michael@0 432 // do nothing
michael@0 433 }
michael@0 434 else {
michael@0 435 // append name to path
michael@0 436 if (needsDelim)
michael@0 437 path += '/';
michael@0 438 path += name;
michael@0 439 needsDelim = true;
michael@0 440 }
michael@0 441 name.Truncate();
michael@0 442 break;
michael@0 443
michael@0 444 default:
michael@0 445 // append char to name
michael@0 446 name += c;
michael@0 447 }
michael@0 448 }
michael@0 449 // append anything left on relativePath (e.g. #..., ;..., ?...)
michael@0 450 if (c != '\0')
michael@0 451 path += Substring(--beg, end);
michael@0 452
michael@0 453 result = path;
michael@0 454 return NS_OK;
michael@0 455 }
michael@0 456
michael@0 457 //----------------------------------------------------------------------------
michael@0 458 // scheme fu
michael@0 459 //----------------------------------------------------------------------------
michael@0 460
michael@0 461 /* Extract URI-Scheme if possible */
michael@0 462 nsresult
michael@0 463 net_ExtractURLScheme(const nsACString &inURI,
michael@0 464 uint32_t *startPos,
michael@0 465 uint32_t *endPos,
michael@0 466 nsACString *scheme)
michael@0 467 {
michael@0 468 // search for something up to a colon, and call it the scheme
michael@0 469 const nsPromiseFlatCString &flatURI = PromiseFlatCString(inURI);
michael@0 470 const char* uri_start = flatURI.get();
michael@0 471 const char* uri = uri_start;
michael@0 472
michael@0 473 if (!uri)
michael@0 474 return NS_ERROR_MALFORMED_URI;
michael@0 475
michael@0 476 // skip leading white space
michael@0 477 while (nsCRT::IsAsciiSpace(*uri))
michael@0 478 uri++;
michael@0 479
michael@0 480 uint32_t start = uri - uri_start;
michael@0 481 if (startPos) {
michael@0 482 *startPos = start;
michael@0 483 }
michael@0 484
michael@0 485 uint32_t length = 0;
michael@0 486 char c;
michael@0 487 while ((c = *uri++) != '\0') {
michael@0 488 // First char must be Alpha
michael@0 489 if (length == 0 && nsCRT::IsAsciiAlpha(c)) {
michael@0 490 length++;
michael@0 491 }
michael@0 492 // Next chars can be alpha + digit + some special chars
michael@0 493 else if (length > 0 && (nsCRT::IsAsciiAlpha(c) ||
michael@0 494 nsCRT::IsAsciiDigit(c) || c == '+' ||
michael@0 495 c == '.' || c == '-')) {
michael@0 496 length++;
michael@0 497 }
michael@0 498 // stop if colon reached but not as first char
michael@0 499 else if (c == ':' && length > 0) {
michael@0 500 if (endPos) {
michael@0 501 *endPos = start + length;
michael@0 502 }
michael@0 503
michael@0 504 if (scheme)
michael@0 505 scheme->Assign(Substring(inURI, start, length));
michael@0 506 return NS_OK;
michael@0 507 }
michael@0 508 else
michael@0 509 break;
michael@0 510 }
michael@0 511 return NS_ERROR_MALFORMED_URI;
michael@0 512 }
michael@0 513
michael@0 514 bool
michael@0 515 net_IsValidScheme(const char *scheme, uint32_t schemeLen)
michael@0 516 {
michael@0 517 // first char must be alpha
michael@0 518 if (!nsCRT::IsAsciiAlpha(*scheme))
michael@0 519 return false;
michael@0 520
michael@0 521 // nsCStrings may have embedded nulls -- reject those too
michael@0 522 for (; schemeLen; ++scheme, --schemeLen) {
michael@0 523 if (!(nsCRT::IsAsciiAlpha(*scheme) ||
michael@0 524 nsCRT::IsAsciiDigit(*scheme) ||
michael@0 525 *scheme == '+' ||
michael@0 526 *scheme == '.' ||
michael@0 527 *scheme == '-'))
michael@0 528 return false;
michael@0 529 }
michael@0 530
michael@0 531 return true;
michael@0 532 }
michael@0 533
michael@0 534 bool
michael@0 535 net_FilterURIString(const char *str, nsACString& result)
michael@0 536 {
michael@0 537 NS_PRECONDITION(str, "Must have a non-null string!");
michael@0 538 bool writing = false;
michael@0 539 result.Truncate();
michael@0 540 const char *p = str;
michael@0 541
michael@0 542 // Remove leading spaces, tabs, CR, LF if any.
michael@0 543 while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') {
michael@0 544 writing = true;
michael@0 545 str = p + 1;
michael@0 546 p++;
michael@0 547 }
michael@0 548
michael@0 549 // Don't strip from the scheme, because other code assumes everything
michael@0 550 // up to the ':' is the scheme, and it's bad not to have it match.
michael@0 551 // If there's no ':', strip.
michael@0 552 bool found_colon = false;
michael@0 553 const char *first = nullptr;
michael@0 554 while (*p) {
michael@0 555 switch (*p) {
michael@0 556 case '\t':
michael@0 557 case '\r':
michael@0 558 case '\n':
michael@0 559 if (found_colon) {
michael@0 560 writing = true;
michael@0 561 // append chars up to but not including *p
michael@0 562 if (p > str)
michael@0 563 result.Append(str, p - str);
michael@0 564 str = p + 1;
michael@0 565 } else {
michael@0 566 // remember where the first \t\r\n was in case we find no scheme
michael@0 567 if (!first)
michael@0 568 first = p;
michael@0 569 }
michael@0 570 break;
michael@0 571
michael@0 572 case ':':
michael@0 573 found_colon = true;
michael@0 574 break;
michael@0 575
michael@0 576 case '/':
michael@0 577 case '@':
michael@0 578 if (!found_colon) {
michael@0 579 // colon also has to precede / or @ to be a scheme
michael@0 580 found_colon = true; // not really, but means ok to strip
michael@0 581 if (first) {
michael@0 582 // go back and replace
michael@0 583 p = first;
michael@0 584 continue; // process *p again
michael@0 585 }
michael@0 586 }
michael@0 587 break;
michael@0 588
michael@0 589 default:
michael@0 590 break;
michael@0 591 }
michael@0 592 p++;
michael@0 593
michael@0 594 // At end, if there was no scheme, and we hit a control char, fix
michael@0 595 // it up now.
michael@0 596 if (!*p && first != nullptr && !found_colon) {
michael@0 597 // TRICKY - to avoid duplicating code, we reset the loop back
michael@0 598 // to the point we found something to do
michael@0 599 p = first;
michael@0 600 // This also stops us from looping after we finish
michael@0 601 found_colon = true; // so we'll replace \t\r\n
michael@0 602 }
michael@0 603 }
michael@0 604
michael@0 605 // Remove trailing spaces if any
michael@0 606 while (((p-1) >= str) && (*(p-1) == ' ')) {
michael@0 607 writing = true;
michael@0 608 p--;
michael@0 609 }
michael@0 610
michael@0 611 if (writing && p > str)
michael@0 612 result.Append(str, p - str);
michael@0 613
michael@0 614 return writing;
michael@0 615 }
michael@0 616
michael@0 617 #if defined(XP_WIN)
michael@0 618 bool
michael@0 619 net_NormalizeFileURL(const nsACString &aURL, nsCString &aResultBuf)
michael@0 620 {
michael@0 621 bool writing = false;
michael@0 622
michael@0 623 nsACString::const_iterator beginIter, endIter;
michael@0 624 aURL.BeginReading(beginIter);
michael@0 625 aURL.EndReading(endIter);
michael@0 626
michael@0 627 const char *s, *begin = beginIter.get();
michael@0 628
michael@0 629 for (s = begin; s != endIter.get(); ++s)
michael@0 630 {
michael@0 631 if (*s == '\\')
michael@0 632 {
michael@0 633 writing = true;
michael@0 634 if (s > begin)
michael@0 635 aResultBuf.Append(begin, s - begin);
michael@0 636 aResultBuf += '/';
michael@0 637 begin = s + 1;
michael@0 638 }
michael@0 639 }
michael@0 640 if (writing && s > begin)
michael@0 641 aResultBuf.Append(begin, s - begin);
michael@0 642
michael@0 643 return writing;
michael@0 644 }
michael@0 645 #endif
michael@0 646
michael@0 647 //----------------------------------------------------------------------------
michael@0 648 // miscellaneous (i.e., stuff that should really be elsewhere)
michael@0 649 //----------------------------------------------------------------------------
michael@0 650
michael@0 651 static inline
michael@0 652 void ToLower(char &c)
michael@0 653 {
michael@0 654 if ((unsigned)(c - 'A') <= (unsigned)('Z' - 'A'))
michael@0 655 c += 'a' - 'A';
michael@0 656 }
michael@0 657
michael@0 658 void
michael@0 659 net_ToLowerCase(char *str, uint32_t length)
michael@0 660 {
michael@0 661 for (char *end = str + length; str < end; ++str)
michael@0 662 ToLower(*str);
michael@0 663 }
michael@0 664
michael@0 665 void
michael@0 666 net_ToLowerCase(char *str)
michael@0 667 {
michael@0 668 for (; *str; ++str)
michael@0 669 ToLower(*str);
michael@0 670 }
michael@0 671
michael@0 672 char *
michael@0 673 net_FindCharInSet(const char *iter, const char *stop, const char *set)
michael@0 674 {
michael@0 675 for (; iter != stop && *iter; ++iter) {
michael@0 676 for (const char *s = set; *s; ++s) {
michael@0 677 if (*iter == *s)
michael@0 678 return (char *) iter;
michael@0 679 }
michael@0 680 }
michael@0 681 return (char *) iter;
michael@0 682 }
michael@0 683
michael@0 684 char *
michael@0 685 net_FindCharNotInSet(const char *iter, const char *stop, const char *set)
michael@0 686 {
michael@0 687 repeat:
michael@0 688 for (const char *s = set; *s; ++s) {
michael@0 689 if (*iter == *s) {
michael@0 690 if (++iter == stop)
michael@0 691 break;
michael@0 692 goto repeat;
michael@0 693 }
michael@0 694 }
michael@0 695 return (char *) iter;
michael@0 696 }
michael@0 697
michael@0 698 char *
michael@0 699 net_RFindCharNotInSet(const char *stop, const char *iter, const char *set)
michael@0 700 {
michael@0 701 --iter;
michael@0 702 --stop;
michael@0 703
michael@0 704 if (iter == stop)
michael@0 705 return (char *) iter;
michael@0 706
michael@0 707 repeat:
michael@0 708 for (const char *s = set; *s; ++s) {
michael@0 709 if (*iter == *s) {
michael@0 710 if (--iter == stop)
michael@0 711 break;
michael@0 712 goto repeat;
michael@0 713 }
michael@0 714 }
michael@0 715 return (char *) iter;
michael@0 716 }
michael@0 717
michael@0 718 #define HTTP_LWS " \t"
michael@0 719
michael@0 720 // Return the index of the closing quote of the string, if any
michael@0 721 static uint32_t
michael@0 722 net_FindStringEnd(const nsCString& flatStr,
michael@0 723 uint32_t stringStart,
michael@0 724 char stringDelim)
michael@0 725 {
michael@0 726 NS_ASSERTION(stringStart < flatStr.Length() &&
michael@0 727 flatStr.CharAt(stringStart) == stringDelim &&
michael@0 728 (stringDelim == '"' || stringDelim == '\''),
michael@0 729 "Invalid stringStart");
michael@0 730
michael@0 731 const char set[] = { stringDelim, '\\', '\0' };
michael@0 732 do {
michael@0 733 // stringStart points to either the start quote or the last
michael@0 734 // escaped char (the char following a '\\')
michael@0 735
michael@0 736 // Write to searchStart here, so that when we get back to the
michael@0 737 // top of the loop right outside this one we search from the
michael@0 738 // right place.
michael@0 739 uint32_t stringEnd = flatStr.FindCharInSet(set, stringStart + 1);
michael@0 740 if (stringEnd == uint32_t(kNotFound))
michael@0 741 return flatStr.Length();
michael@0 742
michael@0 743 if (flatStr.CharAt(stringEnd) == '\\') {
michael@0 744 // Hit a backslash-escaped char. Need to skip over it.
michael@0 745 stringStart = stringEnd + 1;
michael@0 746 if (stringStart == flatStr.Length())
michael@0 747 return stringStart;
michael@0 748
michael@0 749 // Go back to looking for the next escape or the string end
michael@0 750 continue;
michael@0 751 }
michael@0 752
michael@0 753 return stringEnd;
michael@0 754
michael@0 755 } while (true);
michael@0 756
michael@0 757 NS_NOTREACHED("How did we get here?");
michael@0 758 return flatStr.Length();
michael@0 759 }
michael@0 760
michael@0 761
michael@0 762 static uint32_t
michael@0 763 net_FindMediaDelimiter(const nsCString& flatStr,
michael@0 764 uint32_t searchStart,
michael@0 765 char delimiter)
michael@0 766 {
michael@0 767 do {
michael@0 768 // searchStart points to the spot from which we should start looking
michael@0 769 // for the delimiter.
michael@0 770 const char delimStr[] = { delimiter, '"', '\0' };
michael@0 771 uint32_t curDelimPos = flatStr.FindCharInSet(delimStr, searchStart);
michael@0 772 if (curDelimPos == uint32_t(kNotFound))
michael@0 773 return flatStr.Length();
michael@0 774
michael@0 775 char ch = flatStr.CharAt(curDelimPos);
michael@0 776 if (ch == delimiter) {
michael@0 777 // Found delimiter
michael@0 778 return curDelimPos;
michael@0 779 }
michael@0 780
michael@0 781 // We hit the start of a quoted string. Look for its end.
michael@0 782 searchStart = net_FindStringEnd(flatStr, curDelimPos, ch);
michael@0 783 if (searchStart == flatStr.Length())
michael@0 784 return searchStart;
michael@0 785
michael@0 786 ++searchStart;
michael@0 787
michael@0 788 // searchStart now points to the first char after the end of the
michael@0 789 // string, so just go back to the top of the loop and look for
michael@0 790 // |delimiter| again.
michael@0 791 } while (true);
michael@0 792
michael@0 793 NS_NOTREACHED("How did we get here?");
michael@0 794 return flatStr.Length();
michael@0 795 }
michael@0 796
michael@0 797 // aOffset should be added to aCharsetStart and aCharsetEnd if this
michael@0 798 // function sets them.
michael@0 799 static void
michael@0 800 net_ParseMediaType(const nsACString &aMediaTypeStr,
michael@0 801 nsACString &aContentType,
michael@0 802 nsACString &aContentCharset,
michael@0 803 int32_t aOffset,
michael@0 804 bool *aHadCharset,
michael@0 805 int32_t *aCharsetStart,
michael@0 806 int32_t *aCharsetEnd)
michael@0 807 {
michael@0 808 const nsCString& flatStr = PromiseFlatCString(aMediaTypeStr);
michael@0 809 const char* start = flatStr.get();
michael@0 810 const char* end = start + flatStr.Length();
michael@0 811
michael@0 812 // Trim LWS leading and trailing whitespace from type. We include '(' in
michael@0 813 // the trailing trim set to catch media-type comments, which are not at all
michael@0 814 // standard, but may occur in rare cases.
michael@0 815 const char* type = net_FindCharNotInSet(start, end, HTTP_LWS);
michael@0 816 const char* typeEnd = net_FindCharInSet(type, end, HTTP_LWS ";(");
michael@0 817
michael@0 818 const char* charset = "";
michael@0 819 const char* charsetEnd = charset;
michael@0 820 int32_t charsetParamStart = 0;
michael@0 821 int32_t charsetParamEnd = 0;
michael@0 822
michael@0 823 // Iterate over parameters
michael@0 824 bool typeHasCharset = false;
michael@0 825 uint32_t paramStart = flatStr.FindChar(';', typeEnd - start);
michael@0 826 if (paramStart != uint32_t(kNotFound)) {
michael@0 827 // We have parameters. Iterate over them.
michael@0 828 uint32_t curParamStart = paramStart + 1;
michael@0 829 do {
michael@0 830 uint32_t curParamEnd =
michael@0 831 net_FindMediaDelimiter(flatStr, curParamStart, ';');
michael@0 832
michael@0 833 const char* paramName = net_FindCharNotInSet(start + curParamStart,
michael@0 834 start + curParamEnd,
michael@0 835 HTTP_LWS);
michael@0 836 static const char charsetStr[] = "charset=";
michael@0 837 if (PL_strncasecmp(paramName, charsetStr,
michael@0 838 sizeof(charsetStr) - 1) == 0) {
michael@0 839 charset = paramName + sizeof(charsetStr) - 1;
michael@0 840 charsetEnd = start + curParamEnd;
michael@0 841 typeHasCharset = true;
michael@0 842 charsetParamStart = curParamStart - 1;
michael@0 843 charsetParamEnd = curParamEnd;
michael@0 844 }
michael@0 845
michael@0 846 curParamStart = curParamEnd + 1;
michael@0 847 } while (curParamStart < flatStr.Length());
michael@0 848 }
michael@0 849
michael@0 850 bool charsetNeedsQuotedStringUnescaping = false;
michael@0 851 if (typeHasCharset) {
michael@0 852 // Trim LWS leading and trailing whitespace from charset. We include
michael@0 853 // '(' in the trailing trim set to catch media-type comments, which are
michael@0 854 // not at all standard, but may occur in rare cases.
michael@0 855 charset = net_FindCharNotInSet(charset, charsetEnd, HTTP_LWS);
michael@0 856 if (*charset == '"') {
michael@0 857 charsetNeedsQuotedStringUnescaping = true;
michael@0 858 charsetEnd =
michael@0 859 start + net_FindStringEnd(flatStr, charset - start, *charset);
michael@0 860 charset++;
michael@0 861 NS_ASSERTION(charsetEnd >= charset, "Bad charset parsing");
michael@0 862 } else {
michael@0 863 charsetEnd = net_FindCharInSet(charset, charsetEnd, HTTP_LWS ";(");
michael@0 864 }
michael@0 865 }
michael@0 866
michael@0 867 // if the server sent "*/*", it is meaningless, so do not store it.
michael@0 868 // also, if type is the same as aContentType, then just update the
michael@0 869 // charset. however, if charset is empty and aContentType hasn't
michael@0 870 // changed, then don't wipe-out an existing aContentCharset. We
michael@0 871 // also want to reject a mime-type if it does not include a slash.
michael@0 872 // some servers give junk after the charset parameter, which may
michael@0 873 // include a comma, so this check makes us a bit more tolerant.
michael@0 874
michael@0 875 if (type != typeEnd && strncmp(type, "*/*", typeEnd - type) != 0 &&
michael@0 876 memchr(type, '/', typeEnd - type) != nullptr) {
michael@0 877 // Common case here is that aContentType is empty
michael@0 878 bool eq = !aContentType.IsEmpty() &&
michael@0 879 aContentType.Equals(Substring(type, typeEnd),
michael@0 880 nsCaseInsensitiveCStringComparator());
michael@0 881 if (!eq) {
michael@0 882 aContentType.Assign(type, typeEnd - type);
michael@0 883 ToLowerCase(aContentType);
michael@0 884 }
michael@0 885
michael@0 886 if ((!eq && *aHadCharset) || typeHasCharset) {
michael@0 887 *aHadCharset = true;
michael@0 888 if (charsetNeedsQuotedStringUnescaping) {
michael@0 889 // parameters using the "quoted-string" syntax need
michael@0 890 // backslash-escapes to be unescaped (see RFC 2616 Section 2.2)
michael@0 891 aContentCharset.Truncate();
michael@0 892 for (const char *c = charset; c != charsetEnd; c++) {
michael@0 893 if (*c == '\\' && c + 1 != charsetEnd) {
michael@0 894 // eat escape
michael@0 895 c++;
michael@0 896 }
michael@0 897 aContentCharset.Append(*c);
michael@0 898 }
michael@0 899 }
michael@0 900 else {
michael@0 901 aContentCharset.Assign(charset, charsetEnd - charset);
michael@0 902 }
michael@0 903 if (typeHasCharset) {
michael@0 904 *aCharsetStart = charsetParamStart + aOffset;
michael@0 905 *aCharsetEnd = charsetParamEnd + aOffset;
michael@0 906 }
michael@0 907 }
michael@0 908 // Only set a new charset position if this is a different type
michael@0 909 // from the last one we had and it doesn't already have a
michael@0 910 // charset param. If this is the same type, we probably want
michael@0 911 // to leave the charset position on its first occurrence.
michael@0 912 if (!eq && !typeHasCharset) {
michael@0 913 int32_t charsetStart = int32_t(paramStart);
michael@0 914 if (charsetStart == kNotFound)
michael@0 915 charsetStart = flatStr.Length();
michael@0 916
michael@0 917 *aCharsetEnd = *aCharsetStart = charsetStart + aOffset;
michael@0 918 }
michael@0 919 }
michael@0 920 }
michael@0 921
michael@0 922 #undef HTTP_LWS
michael@0 923
michael@0 924 void
michael@0 925 net_ParseContentType(const nsACString &aHeaderStr,
michael@0 926 nsACString &aContentType,
michael@0 927 nsACString &aContentCharset,
michael@0 928 bool *aHadCharset)
michael@0 929 {
michael@0 930 int32_t dummy1, dummy2;
michael@0 931 net_ParseContentType(aHeaderStr, aContentType, aContentCharset,
michael@0 932 aHadCharset, &dummy1, &dummy2);
michael@0 933 }
michael@0 934
michael@0 935 void
michael@0 936 net_ParseContentType(const nsACString &aHeaderStr,
michael@0 937 nsACString &aContentType,
michael@0 938 nsACString &aContentCharset,
michael@0 939 bool *aHadCharset,
michael@0 940 int32_t *aCharsetStart,
michael@0 941 int32_t *aCharsetEnd)
michael@0 942 {
michael@0 943 //
michael@0 944 // Augmented BNF (from RFC 2616 section 3.7):
michael@0 945 //
michael@0 946 // header-value = media-type *( LWS "," LWS media-type )
michael@0 947 // media-type = type "/" subtype *( LWS ";" LWS parameter )
michael@0 948 // type = token
michael@0 949 // subtype = token
michael@0 950 // parameter = attribute "=" value
michael@0 951 // attribute = token
michael@0 952 // value = token | quoted-string
michael@0 953 //
michael@0 954 //
michael@0 955 // Examples:
michael@0 956 //
michael@0 957 // text/html
michael@0 958 // text/html, text/html
michael@0 959 // text/html,text/html; charset=ISO-8859-1
michael@0 960 // text/html,text/html; charset="ISO-8859-1"
michael@0 961 // text/html;charset=ISO-8859-1, text/html
michael@0 962 // text/html;charset='ISO-8859-1', text/html
michael@0 963 // application/octet-stream
michael@0 964 //
michael@0 965
michael@0 966 *aHadCharset = false;
michael@0 967 const nsCString& flatStr = PromiseFlatCString(aHeaderStr);
michael@0 968
michael@0 969 // iterate over media-types. Note that ',' characters can happen
michael@0 970 // inside quoted strings, so we need to watch out for that.
michael@0 971 uint32_t curTypeStart = 0;
michael@0 972 do {
michael@0 973 // curTypeStart points to the start of the current media-type. We want
michael@0 974 // to look for its end.
michael@0 975 uint32_t curTypeEnd =
michael@0 976 net_FindMediaDelimiter(flatStr, curTypeStart, ',');
michael@0 977
michael@0 978 // At this point curTypeEnd points to the spot where the media-type
michael@0 979 // starting at curTypeEnd ends. Time to parse that!
michael@0 980 net_ParseMediaType(Substring(flatStr, curTypeStart,
michael@0 981 curTypeEnd - curTypeStart),
michael@0 982 aContentType, aContentCharset, curTypeStart,
michael@0 983 aHadCharset, aCharsetStart, aCharsetEnd);
michael@0 984
michael@0 985 // And let's move on to the next media-type
michael@0 986 curTypeStart = curTypeEnd + 1;
michael@0 987 } while (curTypeStart < flatStr.Length());
michael@0 988 }
michael@0 989
michael@0 990 bool
michael@0 991 net_IsValidHostName(const nsCSubstring &host)
michael@0 992 {
michael@0 993 const char *end = host.EndReading();
michael@0 994 // Use explicit whitelists to select which characters we are
michael@0 995 // willing to send to lower-level DNS logic. This is more
michael@0 996 // self-documenting, and can also be slightly faster than the
michael@0 997 // blacklist approach, since DNS names are the common case, and
michael@0 998 // the commonest characters will tend to be near the start of
michael@0 999 // the list.
michael@0 1000
michael@0 1001 // Whitelist for DNS names (RFC 1035) with extra characters added
michael@0 1002 // for pragmatic reasons "$+_"
michael@0 1003 // see https://bugzilla.mozilla.org/show_bug.cgi?id=355181#c2
michael@0 1004 if (net_FindCharNotInSet(host.BeginReading(), end,
michael@0 1005 "abcdefghijklmnopqrstuvwxyz"
michael@0 1006 ".-0123456789"
michael@0 1007 "ABCDEFGHIJKLMNOPQRSTUVWXYZ$+_") == end)
michael@0 1008 return true;
michael@0 1009
michael@0 1010 // Might be a valid IPv6 link-local address containing a percent sign
michael@0 1011 nsAutoCString strhost(host);
michael@0 1012 PRNetAddr addr;
michael@0 1013 return PR_StringToNetAddr(strhost.get(), &addr) == PR_SUCCESS;
michael@0 1014 }
michael@0 1015
michael@0 1016 bool
michael@0 1017 net_IsValidIPv4Addr(const char *addr, int32_t addrLen)
michael@0 1018 {
michael@0 1019 RangedPtr<const char> p(addr, addrLen);
michael@0 1020
michael@0 1021 int32_t octet = -1; // means no digit yet
michael@0 1022 int32_t dotCount = 0; // number of dots in the address
michael@0 1023
michael@0 1024 for (; addrLen; ++p, --addrLen) {
michael@0 1025 if (*p == '.') {
michael@0 1026 dotCount++;
michael@0 1027 if (octet == -1) {
michael@0 1028 // invalid octet
michael@0 1029 return false;
michael@0 1030 }
michael@0 1031 octet = -1;
michael@0 1032 } else if (*p >= '0' && *p <='9') {
michael@0 1033 if (octet == 0) {
michael@0 1034 // leading 0 is not allowed
michael@0 1035 return false;
michael@0 1036 } else if (octet == -1) {
michael@0 1037 octet = *p - '0';
michael@0 1038 } else {
michael@0 1039 octet *= 10;
michael@0 1040 octet += *p - '0';
michael@0 1041 if (octet > 255)
michael@0 1042 return false;
michael@0 1043 }
michael@0 1044 } else {
michael@0 1045 // invalid character
michael@0 1046 return false;
michael@0 1047 }
michael@0 1048 }
michael@0 1049
michael@0 1050 return (dotCount == 3 && octet != -1);
michael@0 1051 }
michael@0 1052
michael@0 1053 bool
michael@0 1054 net_IsValidIPv6Addr(const char *addr, int32_t addrLen)
michael@0 1055 {
michael@0 1056 RangedPtr<const char> p(addr, addrLen);
michael@0 1057
michael@0 1058 int32_t digits = 0; // number of digits in current block
michael@0 1059 int32_t colons = 0; // number of colons in a row during parsing
michael@0 1060 int32_t blocks = 0; // number of hexadecimal blocks
michael@0 1061 bool haveZeros = false; // true if double colon is present in the address
michael@0 1062
michael@0 1063 for (; addrLen; ++p, --addrLen) {
michael@0 1064 if (*p == ':') {
michael@0 1065 if (colons == 0) {
michael@0 1066 if (digits != 0) {
michael@0 1067 digits = 0;
michael@0 1068 blocks++;
michael@0 1069 }
michael@0 1070 } else if (colons == 1) {
michael@0 1071 if (haveZeros)
michael@0 1072 return false; // only one occurrence is allowed
michael@0 1073 haveZeros = true;
michael@0 1074 } else {
michael@0 1075 // too many colons in a row
michael@0 1076 return false;
michael@0 1077 }
michael@0 1078 colons++;
michael@0 1079 } else if ((*p >= '0' && *p <= '9') || (*p >= 'a' && *p <= 'f') ||
michael@0 1080 (*p >= 'A' && *p <= 'F')) {
michael@0 1081 if (colons == 1 && blocks == 0) // starts with a single colon
michael@0 1082 return false;
michael@0 1083 if (digits == 4) // too many digits
michael@0 1084 return false;
michael@0 1085 colons = 0;
michael@0 1086 digits++;
michael@0 1087 } else if (*p == '.') {
michael@0 1088 // check valid IPv4 from the beginning of the last block
michael@0 1089 if (!net_IsValidIPv4Addr(p.get() - digits, addrLen + digits))
michael@0 1090 return false;
michael@0 1091 return (haveZeros && blocks < 6) || (!haveZeros && blocks == 6);
michael@0 1092 } else {
michael@0 1093 // invalid character
michael@0 1094 return false;
michael@0 1095 }
michael@0 1096 }
michael@0 1097
michael@0 1098 if (colons == 1) // ends with a single colon
michael@0 1099 return false;
michael@0 1100
michael@0 1101 if (digits) // there is a block at the end
michael@0 1102 blocks++;
michael@0 1103
michael@0 1104 return (haveZeros && blocks < 8) || (!haveZeros && blocks == 8);
michael@0 1105 }

mercurial