1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set ts=4 sw=4 sts=4 et cin: */
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 // HttpLog.h should generally be included first
8 #include "HttpLog.h"
9 
10 #include "nsHttp.h"
11 #include "CacheControlParser.h"
12 #include "PLDHashTable.h"
13 #include "mozilla/Mutex.h"
14 #include "mozilla/HashFunctions.h"
15 #include "nsCRT.h"
16 #include "nsHttpRequestHead.h"
17 #include "nsHttpResponseHead.h"
18 #include "nsHttpHandler.h"
19 #include "nsICacheEntry.h"
20 #include "nsIRequest.h"
21 #include <errno.h>
22 #include <functional>
23 
24 namespace mozilla {
25 namespace net {
26 
27 // define storage for all atoms
28 namespace nsHttp {
29 #define HTTP_ATOM(_name, _value) nsHttpAtom _name = {_value};
30 #include "nsHttpAtomList.h"
31 #undef HTTP_ATOM
32 }
33 
34 // find out how many atoms we have
35 #define HTTP_ATOM(_name, _value) Unused_##_name,
36 enum {
37 #include "nsHttpAtomList.h"
38   NUM_HTTP_ATOMS
39 };
40 #undef HTTP_ATOM
41 
42 // we keep a linked list of atoms allocated on the heap for easy clean up when
43 // the atom table is destroyed.  The structure and value string are allocated
44 // as one contiguous block.
45 
46 struct HttpHeapAtom {
47   struct HttpHeapAtom *next;
48   char value[1];
49 };
50 
51 static PLDHashTable *sAtomTable;
52 static struct HttpHeapAtom *sHeapAtoms = nullptr;
53 static Mutex *sLock = nullptr;
54 
NewHeapAtom(const char * value)55 HttpHeapAtom *NewHeapAtom(const char *value) {
56   int len = strlen(value);
57 
58   HttpHeapAtom *a = reinterpret_cast<HttpHeapAtom *>(malloc(sizeof(*a) + len));
59   if (!a) return nullptr;
60   memcpy(a->value, value, len + 1);
61 
62   // add this heap atom to the list of all heap atoms
63   a->next = sHeapAtoms;
64   sHeapAtoms = a;
65 
66   return a;
67 }
68 
69 // Hash string ignore case, based on PL_HashString
StringHash(const void * key)70 static PLDHashNumber StringHash(const void *key) {
71   PLDHashNumber h = 0;
72   for (const char *s = reinterpret_cast<const char *>(key); *s; ++s)
73     h = AddToHash(h, nsCRT::ToLower(*s));
74   return h;
75 }
76 
StringCompare(const PLDHashEntryHdr * entry,const void * testKey)77 static bool StringCompare(const PLDHashEntryHdr *entry, const void *testKey) {
78   const void *entryKey = reinterpret_cast<const PLDHashEntryStub *>(entry)->key;
79 
80   return PL_strcasecmp(reinterpret_cast<const char *>(entryKey),
81                        reinterpret_cast<const char *>(testKey)) == 0;
82 }
83 
84 static const PLDHashTableOps ops = {StringHash, StringCompare,
85                                     PLDHashTable::MoveEntryStub,
86                                     PLDHashTable::ClearEntryStub, nullptr};
87 
88 // We put the atoms in a hash table for speedy lookup.. see ResolveAtom.
89 namespace nsHttp {
CreateAtomTable()90 nsresult CreateAtomTable() {
91   MOZ_ASSERT(!sAtomTable, "atom table already initialized");
92 
93   if (!sLock) {
94     sLock = new Mutex("nsHttp.sLock");
95   }
96 
97   // The initial length for this table is a value greater than the number of
98   // known atoms (NUM_HTTP_ATOMS) because we expect to encounter a few random
99   // headers right off the bat.
100   sAtomTable =
101       new PLDHashTable(&ops, sizeof(PLDHashEntryStub), NUM_HTTP_ATOMS + 10);
102 
103   // fill the table with our known atoms
104   const char *const atoms[] = {
105 #define HTTP_ATOM(_name, _value) _name._val,
106 #include "nsHttpAtomList.h"
107 #undef HTTP_ATOM
108       nullptr};
109 
110   for (int i = 0; atoms[i]; ++i) {
111     auto stub =
112         static_cast<PLDHashEntryStub *>(sAtomTable->Add(atoms[i], fallible));
113     if (!stub) return NS_ERROR_OUT_OF_MEMORY;
114 
115     MOZ_ASSERT(!stub->key, "duplicate static atom");
116     stub->key = atoms[i];
117   }
118 
119   return NS_OK;
120 }
121 
DestroyAtomTable()122 void DestroyAtomTable() {
123   delete sAtomTable;
124   sAtomTable = nullptr;
125 
126   while (sHeapAtoms) {
127     HttpHeapAtom *next = sHeapAtoms->next;
128     free(sHeapAtoms);
129     sHeapAtoms = next;
130   }
131 
132   delete sLock;
133   sLock = nullptr;
134 }
135 
GetLock()136 Mutex *GetLock() { return sLock; }
137 
138 // this function may be called from multiple threads
ResolveAtom(const char * str)139 nsHttpAtom ResolveAtom(const char *str) {
140   nsHttpAtom atom = {nullptr};
141 
142   if (!str || !sAtomTable) return atom;
143 
144   MutexAutoLock lock(*sLock);
145 
146   auto stub = static_cast<PLDHashEntryStub *>(sAtomTable->Add(str, fallible));
147   if (!stub) return atom;  // out of memory
148 
149   if (stub->key) {
150     atom._val = reinterpret_cast<const char *>(stub->key);
151     return atom;
152   }
153 
154   // if the atom could not be found in the atom table, then we'll go
155   // and allocate a new atom on the heap.
156   HttpHeapAtom *heapAtom = NewHeapAtom(str);
157   if (!heapAtom) return atom;  // out of memory
158 
159   stub->key = atom._val = heapAtom->value;
160   return atom;
161 }
162 
163 //
164 // From section 2.2 of RFC 2616, a token is defined as:
165 //
166 //   token          = 1*<any CHAR except CTLs or separators>
167 //   CHAR           = <any US-ASCII character (octets 0 - 127)>
168 //   separators     = "(" | ")" | "<" | ">" | "@"
169 //                  | "," | ";" | ":" | "\" | <">
170 //                  | "/" | "[" | "]" | "?" | "="
171 //                  | "{" | "}" | SP | HT
172 //   CTL            = <any US-ASCII control character
173 //                    (octets 0 - 31) and DEL (127)>
174 //   SP             = <US-ASCII SP, space (32)>
175 //   HT             = <US-ASCII HT, horizontal-tab (9)>
176 //
177 static const char kValidTokenMap[128] = {
178     0, 0, 0, 0, 0, 0, 0, 0,  //   0
179     0, 0, 0, 0, 0, 0, 0, 0,  //   8
180     0, 0, 0, 0, 0, 0, 0, 0,  //  16
181     0, 0, 0, 0, 0, 0, 0, 0,  //  24
182 
183     0, 1, 0, 1, 1, 1, 1, 1,  //  32
184     0, 0, 1, 1, 0, 1, 1, 0,  //  40
185     1, 1, 1, 1, 1, 1, 1, 1,  //  48
186     1, 1, 0, 0, 0, 0, 0, 0,  //  56
187 
188     0, 1, 1, 1, 1, 1, 1, 1,  //  64
189     1, 1, 1, 1, 1, 1, 1, 1,  //  72
190     1, 1, 1, 1, 1, 1, 1, 1,  //  80
191     1, 1, 1, 0, 0, 0, 1, 1,  //  88
192 
193     1, 1, 1, 1, 1, 1, 1, 1,  //  96
194     1, 1, 1, 1, 1, 1, 1, 1,  // 104
195     1, 1, 1, 1, 1, 1, 1, 1,  // 112
196     1, 1, 1, 0, 1, 0, 1, 0   // 120
197 };
IsValidToken(const char * start,const char * end)198 bool IsValidToken(const char *start, const char *end) {
199   if (start == end) return false;
200 
201   for (; start != end; ++start) {
202     const unsigned char idx = *start;
203     if (idx > 127 || !kValidTokenMap[idx]) return false;
204   }
205 
206   return true;
207 }
208 
GetProtocolVersion(uint32_t pv)209 const char *GetProtocolVersion(uint32_t pv) {
210   switch (pv) {
211     case HTTP_VERSION_2:
212     case NS_HTTP_VERSION_2_0:
213       return "h2";
214     case NS_HTTP_VERSION_1_0:
215       return "http/1.0";
216     case NS_HTTP_VERSION_1_1:
217       return "http/1.1";
218     default:
219       NS_WARNING(nsPrintfCString("Unkown protocol version: 0x%X. "
220                                  "Please file a bug",
221                                  pv)
222                      .get());
223       return "http/1.1";
224   }
225 }
226 
227 // static
TrimHTTPWhitespace(const nsACString & aSource,nsACString & aDest)228 void TrimHTTPWhitespace(const nsACString &aSource, nsACString &aDest) {
229   nsAutoCString str(aSource);
230 
231   // HTTP whitespace 0x09: '\t', 0x0A: '\n', 0x0D: '\r', 0x20: ' '
232   static const char kHTTPWhitespace[] = "\t\n\r ";
233   str.Trim(kHTTPWhitespace);
234   aDest.Assign(str);
235 }
236 
237 // static
IsReasonableHeaderValue(const nsACString & s)238 bool IsReasonableHeaderValue(const nsACString &s) {
239   // Header values MUST NOT contain line-breaks.  RFC 2616 technically
240   // permits CTL characters, including CR and LF, in header values provided
241   // they are quoted.  However, this can lead to problems if servers do not
242   // interpret quoted strings properly.  Disallowing CR and LF here seems
243   // reasonable and keeps things simple.  We also disallow a null byte.
244   const nsACString::char_type *end = s.EndReading();
245   for (const nsACString::char_type *i = s.BeginReading(); i != end; ++i) {
246     if (*i == '\r' || *i == '\n' || *i == '\0') {
247       return false;
248     }
249   }
250   return true;
251 }
252 
FindToken(const char * input,const char * token,const char * seps)253 const char *FindToken(const char *input, const char *token, const char *seps) {
254   if (!input) return nullptr;
255 
256   int inputLen = strlen(input);
257   int tokenLen = strlen(token);
258 
259   if (inputLen < tokenLen) return nullptr;
260 
261   const char *inputTop = input;
262   const char *inputEnd = input + inputLen - tokenLen;
263   for (; input <= inputEnd; ++input) {
264     if (PL_strncasecmp(input, token, tokenLen) == 0) {
265       if (input > inputTop && !strchr(seps, *(input - 1))) continue;
266       if (input < inputEnd && !strchr(seps, *(input + tokenLen))) continue;
267       return input;
268     }
269   }
270 
271   return nullptr;
272 }
273 
ParseInt64(const char * input,const char ** next,int64_t * r)274 bool ParseInt64(const char *input, const char **next, int64_t *r) {
275   MOZ_ASSERT(input);
276   MOZ_ASSERT(r);
277 
278   char *end = nullptr;
279   errno = 0;  // Clear errno to make sure its value is set by strtoll
280   int64_t value = strtoll(input, &end, /* base */ 10);
281 
282   // Fail if: - the parsed number overflows.
283   //          - the end points to the start of the input string.
284   //          - we parsed a negative value. Consumers don't expect that.
285   if (errno != 0 || end == input || value < 0) {
286     LOG(("nsHttp::ParseInt64 value=%" PRId64 " errno=%d", value, errno));
287     return false;
288   }
289 
290   if (next) {
291     *next = end;
292   }
293   *r = value;
294   return true;
295 }
296 
IsPermanentRedirect(uint32_t httpStatus)297 bool IsPermanentRedirect(uint32_t httpStatus) {
298   return httpStatus == 301 || httpStatus == 308;
299 }
300 
ValidationRequired(bool isForcedValid,nsHttpResponseHead * cachedResponseHead,uint32_t loadFlags,bool allowStaleCacheContent,bool isImmutable,bool customConditionalRequest,nsHttpRequestHead & requestHead,nsICacheEntry * entry,CacheControlParser & cacheControlRequest,bool fromPreviousSession)301 bool ValidationRequired(bool isForcedValid,
302                         nsHttpResponseHead *cachedResponseHead,
303                         uint32_t loadFlags, bool allowStaleCacheContent,
304                         bool isImmutable, bool customConditionalRequest,
305                         nsHttpRequestHead &requestHead, nsICacheEntry *entry,
306                         CacheControlParser &cacheControlRequest,
307                         bool fromPreviousSession) {
308   // Check isForcedValid to see if it is possible to skip validation.
309   // Don't skip validation if we have serious reason to believe that this
310   // content is invalid (it's expired).
311   // See netwerk/cache2/nsICacheEntry.idl for details
312   if (isForcedValid && (!cachedResponseHead->ExpiresInPast() ||
313                         !cachedResponseHead->MustValidateIfExpired())) {
314     LOG(("NOT validating based on isForcedValid being true.\n"));
315     return false;
316   }
317 
318   // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
319   if (loadFlags & nsIRequest::LOAD_FROM_CACHE || allowStaleCacheContent) {
320     LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
321     return false;
322   }
323 
324   // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
325   // it's revalidated with the server.
326   if ((loadFlags & nsIRequest::VALIDATE_ALWAYS) && !isImmutable) {
327     LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
328     return true;
329   }
330 
331   // Even if the VALIDATE_NEVER flag is set, there are still some cases in
332   // which we must validate the cached response with the server.
333   if (loadFlags & nsIRequest::VALIDATE_NEVER) {
334     LOG(("VALIDATE_NEVER set\n"));
335     // if no-store validate cached response (see bug 112564)
336     if (cachedResponseHead->NoStore()) {
337       LOG(("Validating based on no-store logic\n"));
338       return true;
339     } else {
340       LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
341       return false;
342     }
343   }
344 
345   // check if validation is strictly required...
346   if (cachedResponseHead->MustValidate()) {
347     LOG(("Validating based on MustValidate() returning TRUE\n"));
348     return true;
349   }
350 
351   // possibly serve from cache for a custom If-Match/If-Unmodified-Since
352   // conditional request
353   if (customConditionalRequest && !requestHead.HasHeader(nsHttp::If_Match) &&
354       !requestHead.HasHeader(nsHttp::If_Unmodified_Since)) {
355     LOG(("Validating based on a custom conditional request\n"));
356     return true;
357   }
358 
359   // previously we also checked for a query-url w/out expiration
360   // and didn't do heuristic on it. but defacto that is allowed now.
361   //
362   // Check if the cache entry has expired...
363 
364   bool doValidation = true;
365   uint32_t now = NowInSeconds();
366 
367   uint32_t age = 0;
368   nsresult rv = cachedResponseHead->ComputeCurrentAge(now, now, &age);
369   if (NS_FAILED(rv)) {
370     return true;
371   }
372 
373   uint32_t freshness = 0;
374   rv = cachedResponseHead->ComputeFreshnessLifetime(&freshness);
375   if (NS_FAILED(rv)) {
376     return true;
377   }
378 
379   uint32_t expiration = 0;
380   rv = entry->GetExpirationTime(&expiration);
381   if (NS_FAILED(rv)) {
382     return true;
383   }
384 
385   uint32_t maxAgeRequest, maxStaleRequest, minFreshRequest;
386 
387   LOG(("  NowInSeconds()=%u, expiration time=%u, freshness lifetime=%u, age=%u",
388        now, expiration, freshness, age));
389 
390   if (cacheControlRequest.NoCache()) {
391     LOG(("  validating, no-cache request"));
392     doValidation = true;
393   } else if (cacheControlRequest.MaxStale(&maxStaleRequest)) {
394     uint32_t staleTime = age > freshness ? age - freshness : 0;
395     doValidation = staleTime > maxStaleRequest;
396     LOG(("  validating=%d, max-stale=%u requested", doValidation,
397          maxStaleRequest));
398   } else if (cacheControlRequest.MaxAge(&maxAgeRequest)) {
399     doValidation = age > maxAgeRequest;
400     LOG(("  validating=%d, max-age=%u requested", doValidation, maxAgeRequest));
401   } else if (cacheControlRequest.MinFresh(&minFreshRequest)) {
402     uint32_t freshTime = freshness > age ? freshness - age : 0;
403     doValidation = freshTime < minFreshRequest;
404     LOG(("  validating=%d, min-fresh=%u requested", doValidation,
405          minFreshRequest));
406   } else if (now <= expiration) {
407     doValidation = false;
408     LOG(("  not validating, expire time not in the past"));
409   } else if (cachedResponseHead->MustValidateIfExpired()) {
410     doValidation = true;
411   } else if (loadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
412     // If the cached response does not include expiration infor-
413     // mation, then we must validate the response, despite whether
414     // or not this is the first access this session.  This behavior
415     // is consistent with existing browsers and is generally expected
416     // by web authors.
417     if (freshness == 0)
418       doValidation = true;
419     else
420       doValidation = fromPreviousSession;
421   } else {
422     doValidation = true;
423   }
424 
425   LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
426   return doValidation;
427 }
428 
GetHttpResponseHeadFromCacheEntry(nsICacheEntry * entry,nsHttpResponseHead * cachedResponseHead)429 nsresult GetHttpResponseHeadFromCacheEntry(
430     nsICacheEntry *entry, nsHttpResponseHead *cachedResponseHead) {
431   nsCString buf;
432   // A "original-response-headers" metadata element holds network original
433   // headers, i.e. the headers in the form as they arrieved from the network. We
434   // need to get the network original headers first, because we need to keep
435   // them in order.
436   nsresult rv = entry->GetMetaDataElement("original-response-headers",
437                                           getter_Copies(buf));
438   if (NS_SUCCEEDED(rv)) {
439     rv = cachedResponseHead->ParseCachedOriginalHeaders((char *)buf.get());
440     if (NS_FAILED(rv)) {
441       LOG(("  failed to parse original-response-headers\n"));
442     }
443   }
444 
445   buf.Adopt(0);
446   // A "response-head" metadata element holds response head, e.g. response
447   // status line and headers in the form Firefox uses them internally (no
448   // dupicate headers, etc.).
449   rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
450   NS_ENSURE_SUCCESS(rv, rv);
451 
452   // Parse string stored in a "response-head" metadata element.
453   // These response headers will be merged with the orignal headers (i.e. the
454   // headers stored in a "original-response-headers" metadata element).
455   rv = cachedResponseHead->ParseCachedHead(buf.get());
456   NS_ENSURE_SUCCESS(rv, rv);
457   buf.Adopt(0);
458 
459   return NS_OK;
460 }
461 
CheckPartial(nsICacheEntry * aEntry,int64_t * aSize,int64_t * aContentLength,nsHttpResponseHead * responseHead)462 nsresult CheckPartial(nsICacheEntry *aEntry, int64_t *aSize,
463                       int64_t *aContentLength,
464                       nsHttpResponseHead *responseHead) {
465   nsresult rv;
466 
467   rv = aEntry->GetDataSize(aSize);
468 
469   if (NS_ERROR_IN_PROGRESS == rv) {
470     *aSize = -1;
471     rv = NS_OK;
472   }
473 
474   NS_ENSURE_SUCCESS(rv, rv);
475 
476   if (!responseHead) {
477     return NS_ERROR_UNEXPECTED;
478   }
479 
480   *aContentLength = responseHead->ContentLength();
481 
482   return NS_OK;
483 }
484 
DetermineFramingAndImmutability(nsICacheEntry * entry,nsHttpResponseHead * responseHead,bool isHttps,bool * weaklyFramed,bool * isImmutable)485 void DetermineFramingAndImmutability(nsICacheEntry *entry,
486                                      nsHttpResponseHead *responseHead,
487                                      bool isHttps, bool *weaklyFramed,
488                                      bool *isImmutable) {
489   nsCString framedBuf;
490   nsresult rv =
491       entry->GetMetaDataElement("strongly-framed", getter_Copies(framedBuf));
492   // describe this in terms of explicitly weakly framed so as to be backwards
493   // compatible with old cache contents which dont have strongly-framed makers
494   *weaklyFramed = NS_SUCCEEDED(rv) && framedBuf.EqualsLiteral("0");
495   *isImmutable = !*weaklyFramed && isHttps && responseHead->Immutable();
496 }
497 
IsBeforeLastActiveTabLoadOptimization(TimeStamp const & when)498 bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const &when) {
499   return gHttpHandler &&
500          gHttpHandler->IsBeforeLastActiveTabLoadOptimization(when);
501 }
502 
NotifyActiveTabLoadOptimization()503 void NotifyActiveTabLoadOptimization() {
504   if (gHttpHandler) {
505     gHttpHandler->NotifyActiveTabLoadOptimization();
506   }
507 }
508 
GetLastActiveTabLoadOptimizationHit()509 TimeStamp const GetLastActiveTabLoadOptimizationHit() {
510   return gHttpHandler ? gHttpHandler->GetLastActiveTabLoadOptimizationHit()
511                       : TimeStamp();
512 }
513 
SetLastActiveTabLoadOptimizationHit(TimeStamp const & when)514 void SetLastActiveTabLoadOptimizationHit(TimeStamp const &when) {
515   if (gHttpHandler) {
516     gHttpHandler->SetLastActiveTabLoadOptimizationHit(when);
517   }
518 }
519 
520 }  // namespace nsHttp
521 
522 template <typename T>
localEnsureBuffer(UniquePtr<T[]> & buf,uint32_t newSize,uint32_t preserve,uint32_t & objSize)523 void localEnsureBuffer(UniquePtr<T[]> &buf, uint32_t newSize, uint32_t preserve,
524                        uint32_t &objSize) {
525   if (objSize >= newSize) return;
526 
527   // Leave a little slop on the new allocation - add 2KB to
528   // what we need and then round the result up to a 4KB (page)
529   // boundary.
530 
531   objSize = (newSize + 2048 + 4095) & ~4095;
532 
533   static_assert(sizeof(T) == 1, "sizeof(T) must be 1");
534   auto tmp = MakeUnique<T[]>(objSize);
535   if (preserve) {
536     memcpy(tmp.get(), buf.get(), preserve);
537   }
538   buf = Move(tmp);
539 }
540 
EnsureBuffer(UniquePtr<char[]> & buf,uint32_t newSize,uint32_t preserve,uint32_t & objSize)541 void EnsureBuffer(UniquePtr<char[]> &buf, uint32_t newSize, uint32_t preserve,
542                   uint32_t &objSize) {
543   localEnsureBuffer<char>(buf, newSize, preserve, objSize);
544 }
545 
EnsureBuffer(UniquePtr<uint8_t[]> & buf,uint32_t newSize,uint32_t preserve,uint32_t & objSize)546 void EnsureBuffer(UniquePtr<uint8_t[]> &buf, uint32_t newSize,
547                   uint32_t preserve, uint32_t &objSize) {
548   localEnsureBuffer<uint8_t>(buf, newSize, preserve, objSize);
549 }
550 
IsTokenSymbol(signed char chr)551 static bool IsTokenSymbol(signed char chr) {
552   if (chr < 33 || chr == 127 || chr == '(' || chr == ')' || chr == '<' ||
553       chr == '>' || chr == '@' || chr == ',' || chr == ';' || chr == ':' ||
554       chr == '"' || chr == '/' || chr == '[' || chr == ']' || chr == '?' ||
555       chr == '=' || chr == '{' || chr == '}' || chr == '\\') {
556     return false;
557   }
558   return true;
559 }
560 
ParsedHeaderPair(const char * name,int32_t nameLen,const char * val,int32_t valLen,bool isQuotedValue)561 ParsedHeaderPair::ParsedHeaderPair(const char *name, int32_t nameLen,
562                                    const char *val, int32_t valLen,
563                                    bool isQuotedValue)
564     : mName(nsDependentCSubstring(nullptr, 0u)),
565       mValue(nsDependentCSubstring(nullptr, 0u)),
566       mIsQuotedValue(isQuotedValue) {
567   if (nameLen > 0) {
568     mName.Rebind(name, name + nameLen);
569   }
570   if (valLen > 0) {
571     if (mIsQuotedValue) {
572       RemoveQuotedStringEscapes(val, valLen);
573       mValue.Rebind(mUnquotedValue.BeginWriting(), mUnquotedValue.Length());
574     } else {
575       mValue.Rebind(val, val + valLen);
576     }
577   }
578 }
579 
RemoveQuotedStringEscapes(const char * val,int32_t valLen)580 void ParsedHeaderPair::RemoveQuotedStringEscapes(const char *val,
581                                                  int32_t valLen) {
582   mUnquotedValue.Truncate();
583   const char *c = val;
584   for (int32_t i = 0; i < valLen; ++i) {
585     if (c[i] == '\\' && c[i + 1]) {
586       ++i;
587     }
588     mUnquotedValue.Append(c[i]);
589   }
590 }
591 
Tokenize(const char * input,uint32_t inputLen,const char token,const std::function<void (const char *,uint32_t)> & consumer)592 static void Tokenize(
593     const char *input, uint32_t inputLen, const char token,
594     const std::function<void(const char *, uint32_t)> &consumer) {
595   auto trimWhitespace = [](const char *in, uint32_t inLen, const char **out,
596                            uint32_t *outLen) {
597     *out = in;
598     *outLen = inLen;
599     if (inLen == 0) {
600       return;
601     }
602 
603     // Trim leading space
604     while (nsCRT::IsAsciiSpace(**out)) {
605       (*out)++;
606       --(*outLen);
607     }
608 
609     // Trim tailing space
610     for (const char *i = *out + *outLen - 1; i >= *out; --i) {
611       if (!nsCRT::IsAsciiSpace(*i)) {
612         break;
613       }
614       --(*outLen);
615     }
616   };
617 
618   const char *first = input;
619   bool inQuote = false;
620   const char *result = nullptr;
621   uint32_t resultLen = 0;
622   for (uint32_t index = 0; index < inputLen; ++index) {
623     if (inQuote && input[index] == '\\' && input[index + 1]) {
624       index++;
625       continue;
626     }
627     if (input[index] == '"') {
628       inQuote = !inQuote;
629       continue;
630     }
631     if (inQuote) {
632       continue;
633     }
634     if (input[index] == token) {
635       trimWhitespace(first, (input + index) - first, &result, &resultLen);
636       consumer(result, resultLen);
637       first = input + index + 1;
638     }
639   }
640 
641   trimWhitespace(first, (input + inputLen) - first, &result, &resultLen);
642   consumer(result, resultLen);
643 }
644 
ParsedHeaderValueList(const char * t,uint32_t len,bool allowInvalidValue)645 ParsedHeaderValueList::ParsedHeaderValueList(const char *t, uint32_t len,
646                                              bool allowInvalidValue) {
647   if (!len) {
648     return;
649   }
650 
651   ParsedHeaderValueList *self = this;
652   auto consumer = [=](const char *output, uint32_t outputLength) {
653     self->ParseNameAndValue(output, allowInvalidValue);
654   };
655 
656   Tokenize(t, len, ';', consumer);
657 }
658 
ParseNameAndValue(const char * input,bool allowInvalidValue)659 void ParsedHeaderValueList::ParseNameAndValue(const char *input,
660                                               bool allowInvalidValue) {
661   const char *nameStart = input;
662   const char *nameEnd = nullptr;
663   const char *valueStart = input;
664   const char *valueEnd = nullptr;
665   bool isQuotedString = false;
666   bool invalidValue = false;
667 
668   for (; *input && *input != ';' && *input != ',' &&
669          !nsCRT::IsAsciiSpace(*input) && *input != '=';
670        input++)
671     ;
672 
673   nameEnd = input;
674 
675   if (!(nameEnd - nameStart)) {
676     return;
677   }
678 
679   // Check whether param name is a valid token.
680   for (const char *c = nameStart; c < nameEnd; c++) {
681     if (!IsTokenSymbol(*c)) {
682       nameEnd = c;
683       break;
684     }
685   }
686 
687   if (!(nameEnd - nameStart)) {
688     return;
689   }
690 
691   while (nsCRT::IsAsciiSpace(*input)) {
692     ++input;
693   }
694 
695   if (!*input || *input++ != '=') {
696     mValues.AppendElement(
697         ParsedHeaderPair(nameStart, nameEnd - nameStart, nullptr, 0, false));
698     return;
699   }
700 
701   while (nsCRT::IsAsciiSpace(*input)) {
702     ++input;
703   }
704 
705   if (*input != '"') {
706     // The value is a token, not a quoted string.
707     valueStart = input;
708     for (valueEnd = input; *valueEnd && !nsCRT::IsAsciiSpace(*valueEnd) &&
709                            *valueEnd != ';' && *valueEnd != ',';
710          valueEnd++)
711       ;
712     input = valueEnd;
713     if (!allowInvalidValue) {
714       for (const char *c = valueStart; c < valueEnd; c++) {
715         if (!IsTokenSymbol(*c)) {
716           valueEnd = c;
717           break;
718         }
719       }
720     }
721   } else {
722     bool foundQuotedEnd = false;
723     isQuotedString = true;
724 
725     ++input;
726     valueStart = input;
727     for (valueEnd = input; *valueEnd; ++valueEnd) {
728       if (*valueEnd == '\\' && *(valueEnd + 1)) {
729         ++valueEnd;
730       } else if (*valueEnd == '"') {
731         foundQuotedEnd = true;
732         break;
733       }
734     }
735     if (!foundQuotedEnd) {
736       invalidValue = true;
737     }
738 
739     input = valueEnd;
740     // *valueEnd != null means that *valueEnd is quote character.
741     if (*valueEnd) {
742       input++;
743     }
744   }
745 
746   if (invalidValue) {
747     valueEnd = valueStart;
748   }
749 
750   mValues.AppendElement(ParsedHeaderPair(nameStart, nameEnd - nameStart,
751                                          valueStart, valueEnd - valueStart,
752                                          isQuotedString));
753 }
754 
ParsedHeaderValueListList(const nsCString & fullHeader,bool allowInvalidValue)755 ParsedHeaderValueListList::ParsedHeaderValueListList(
756     const nsCString &fullHeader, bool allowInvalidValue)
757     : mFull(fullHeader) {
758   auto &values = mValues;
759   auto consumer = [&values, allowInvalidValue](const char *output,
760                                                uint32_t outputLength) {
761     values.AppendElement(
762         ParsedHeaderValueList(output, outputLength, allowInvalidValue));
763   };
764 
765   Tokenize(mFull.BeginReading(), mFull.Length(), ',', consumer);
766 }
767 
768 }  // namespace net
769 }  // namespace mozilla
770