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