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