1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=4 sw=2 sts=2 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 "mozilla/Unused.h"
11 #include "nsHttpResponseHead.h"
12 #include "nsIHttpHeaderVisitor.h"
13 #include "nsPrintfCString.h"
14 #include "prtime.h"
15 #include "plstr.h"
16 #include "nsCRT.h"
17 #include "nsURLHelper.h"
18 #include "CacheControlParser.h"
19 #include <algorithm>
20
21 namespace mozilla {
22 namespace net {
23
24 //-----------------------------------------------------------------------------
25 // nsHttpResponseHead <public>
26 //-----------------------------------------------------------------------------
27
28 // Note that the code below MUST be synchronized with the IPC
29 // serialization/deserialization in PHttpChannelParams.h.
nsHttpResponseHead(const nsHttpResponseHead & aOther)30 nsHttpResponseHead::nsHttpResponseHead(const nsHttpResponseHead& aOther) {
31 nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
32 RecursiveMutexAutoLock monitor(other.mRecursiveMutex);
33
34 mHeaders = other.mHeaders;
35 mVersion = other.mVersion;
36 mStatus = other.mStatus;
37 mStatusText = other.mStatusText;
38 mContentLength = other.mContentLength;
39 mContentType = other.mContentType;
40 mContentCharset = other.mContentCharset;
41 mHasCacheControl = other.mHasCacheControl;
42 mCacheControlPublic = other.mCacheControlPublic;
43 mCacheControlPrivate = other.mCacheControlPrivate;
44 mCacheControlNoStore = other.mCacheControlNoStore;
45 mCacheControlNoCache = other.mCacheControlNoCache;
46 mCacheControlImmutable = other.mCacheControlImmutable;
47 mCacheControlStaleWhileRevalidateSet =
48 other.mCacheControlStaleWhileRevalidateSet;
49 mCacheControlStaleWhileRevalidate = other.mCacheControlStaleWhileRevalidate;
50 mCacheControlMaxAgeSet = other.mCacheControlMaxAgeSet;
51 mCacheControlMaxAge = other.mCacheControlMaxAge;
52 mPragmaNoCache = other.mPragmaNoCache;
53 }
54
operator =(const nsHttpResponseHead & aOther)55 nsHttpResponseHead& nsHttpResponseHead::operator=(
56 const nsHttpResponseHead& aOther) {
57 nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
58 RecursiveMutexAutoLock monitorOther(other.mRecursiveMutex);
59 RecursiveMutexAutoLock monitor(mRecursiveMutex);
60
61 mHeaders = other.mHeaders;
62 mVersion = other.mVersion;
63 mStatus = other.mStatus;
64 mStatusText = other.mStatusText;
65 mContentLength = other.mContentLength;
66 mContentType = other.mContentType;
67 mContentCharset = other.mContentCharset;
68 mHasCacheControl = other.mHasCacheControl;
69 mCacheControlPublic = other.mCacheControlPublic;
70 mCacheControlPrivate = other.mCacheControlPrivate;
71 mCacheControlNoStore = other.mCacheControlNoStore;
72 mCacheControlNoCache = other.mCacheControlNoCache;
73 mCacheControlImmutable = other.mCacheControlImmutable;
74 mCacheControlStaleWhileRevalidateSet =
75 other.mCacheControlStaleWhileRevalidateSet;
76 mCacheControlStaleWhileRevalidate = other.mCacheControlStaleWhileRevalidate;
77 mCacheControlMaxAgeSet = other.mCacheControlMaxAgeSet;
78 mCacheControlMaxAge = other.mCacheControlMaxAge;
79 mPragmaNoCache = other.mPragmaNoCache;
80
81 return *this;
82 }
83
Version()84 HttpVersion nsHttpResponseHead::Version() {
85 RecursiveMutexAutoLock monitor(mRecursiveMutex);
86 return mVersion;
87 }
88
Status() const89 uint16_t nsHttpResponseHead::Status() const {
90 RecursiveMutexAutoLock monitor(mRecursiveMutex);
91 return mStatus;
92 }
93
StatusText(nsACString & aStatusText)94 void nsHttpResponseHead::StatusText(nsACString& aStatusText) {
95 RecursiveMutexAutoLock monitor(mRecursiveMutex);
96 aStatusText = mStatusText;
97 }
98
ContentLength()99 int64_t nsHttpResponseHead::ContentLength() {
100 RecursiveMutexAutoLock monitor(mRecursiveMutex);
101 return mContentLength;
102 }
103
ContentType(nsACString & aContentType) const104 void nsHttpResponseHead::ContentType(nsACString& aContentType) const {
105 RecursiveMutexAutoLock monitor(mRecursiveMutex);
106 aContentType = mContentType;
107 }
108
ContentCharset(nsACString & aContentCharset)109 void nsHttpResponseHead::ContentCharset(nsACString& aContentCharset) {
110 RecursiveMutexAutoLock monitor(mRecursiveMutex);
111 aContentCharset = mContentCharset;
112 }
113
Public()114 bool nsHttpResponseHead::Public() {
115 RecursiveMutexAutoLock monitor(mRecursiveMutex);
116 return mCacheControlPublic;
117 }
118
Private()119 bool nsHttpResponseHead::Private() {
120 RecursiveMutexAutoLock monitor(mRecursiveMutex);
121 return mCacheControlPrivate;
122 }
123
NoStore()124 bool nsHttpResponseHead::NoStore() {
125 RecursiveMutexAutoLock monitor(mRecursiveMutex);
126 return mCacheControlNoStore;
127 }
128
NoCache()129 bool nsHttpResponseHead::NoCache() {
130 RecursiveMutexAutoLock monitor(mRecursiveMutex);
131 return NoCache_locked();
132 }
133
Immutable()134 bool nsHttpResponseHead::Immutable() {
135 RecursiveMutexAutoLock monitor(mRecursiveMutex);
136 return mCacheControlImmutable;
137 }
138
SetHeader(const nsACString & hdr,const nsACString & val,bool merge)139 nsresult nsHttpResponseHead::SetHeader(const nsACString& hdr,
140 const nsACString& val, bool merge) {
141 RecursiveMutexAutoLock monitor(mRecursiveMutex);
142
143 if (mInVisitHeaders) {
144 return NS_ERROR_FAILURE;
145 }
146
147 nsHttpAtom atom = nsHttp::ResolveAtom(hdr);
148 if (!atom) {
149 NS_WARNING("failed to resolve atom");
150 return NS_ERROR_NOT_AVAILABLE;
151 }
152
153 return SetHeader_locked(atom, hdr, val, merge);
154 }
155
SetHeader(const nsHttpAtom & hdr,const nsACString & val,bool merge)156 nsresult nsHttpResponseHead::SetHeader(const nsHttpAtom& hdr,
157 const nsACString& val, bool merge) {
158 RecursiveMutexAutoLock monitor(mRecursiveMutex);
159
160 if (mInVisitHeaders) {
161 return NS_ERROR_FAILURE;
162 }
163
164 return SetHeader_locked(hdr, ""_ns, val, merge);
165 }
166
SetHeader_locked(const nsHttpAtom & atom,const nsACString & hdr,const nsACString & val,bool merge)167 nsresult nsHttpResponseHead::SetHeader_locked(const nsHttpAtom& atom,
168 const nsACString& hdr,
169 const nsACString& val,
170 bool merge) {
171 nsresult rv = mHeaders.SetHeader(atom, hdr, val, merge,
172 nsHttpHeaderArray::eVarietyResponse);
173 if (NS_FAILED(rv)) return rv;
174
175 // respond to changes in these headers. we need to reparse the entire
176 // header since the change may have merged in additional values.
177 if (atom == nsHttp::Cache_Control) {
178 ParseCacheControl(mHeaders.PeekHeader(atom));
179 } else if (atom == nsHttp::Pragma) {
180 ParsePragma(mHeaders.PeekHeader(atom));
181 }
182
183 return NS_OK;
184 }
185
GetHeader(const nsHttpAtom & h,nsACString & v)186 nsresult nsHttpResponseHead::GetHeader(const nsHttpAtom& h, nsACString& v) {
187 v.Truncate();
188 RecursiveMutexAutoLock monitor(mRecursiveMutex);
189 return mHeaders.GetHeader(h, v);
190 }
191
ClearHeader(const nsHttpAtom & h)192 void nsHttpResponseHead::ClearHeader(const nsHttpAtom& h) {
193 RecursiveMutexAutoLock monitor(mRecursiveMutex);
194 mHeaders.ClearHeader(h);
195 }
196
ClearHeaders()197 void nsHttpResponseHead::ClearHeaders() {
198 RecursiveMutexAutoLock monitor(mRecursiveMutex);
199 mHeaders.Clear();
200 }
201
HasHeaderValue(const nsHttpAtom & h,const char * v)202 bool nsHttpResponseHead::HasHeaderValue(const nsHttpAtom& h, const char* v) {
203 RecursiveMutexAutoLock monitor(mRecursiveMutex);
204 return mHeaders.HasHeaderValue(h, v);
205 }
206
HasHeader(const nsHttpAtom & h) const207 bool nsHttpResponseHead::HasHeader(const nsHttpAtom& h) const {
208 RecursiveMutexAutoLock monitor(mRecursiveMutex);
209 return mHeaders.HasHeader(h);
210 }
211
SetContentType(const nsACString & s)212 void nsHttpResponseHead::SetContentType(const nsACString& s) {
213 RecursiveMutexAutoLock monitor(mRecursiveMutex);
214 mContentType = s;
215 }
216
SetContentCharset(const nsACString & s)217 void nsHttpResponseHead::SetContentCharset(const nsACString& s) {
218 RecursiveMutexAutoLock monitor(mRecursiveMutex);
219 mContentCharset = s;
220 }
221
SetContentLength(int64_t len)222 void nsHttpResponseHead::SetContentLength(int64_t len) {
223 RecursiveMutexAutoLock monitor(mRecursiveMutex);
224
225 mContentLength = len;
226 if (len < 0) {
227 mHeaders.ClearHeader(nsHttp::Content_Length);
228 } else {
229 DebugOnly<nsresult> rv = mHeaders.SetHeader(
230 nsHttp::Content_Length, nsPrintfCString("%" PRId64, len), false,
231 nsHttpHeaderArray::eVarietyResponse);
232 MOZ_ASSERT(NS_SUCCEEDED(rv));
233 }
234 }
235
Flatten(nsACString & buf,bool pruneTransients)236 void nsHttpResponseHead::Flatten(nsACString& buf, bool pruneTransients) {
237 RecursiveMutexAutoLock monitor(mRecursiveMutex);
238 if (mVersion == HttpVersion::v0_9) return;
239
240 buf.AppendLiteral("HTTP/");
241 if (mVersion == HttpVersion::v3_0) {
242 buf.AppendLiteral("3 ");
243 } else if (mVersion == HttpVersion::v2_0) {
244 buf.AppendLiteral("2 ");
245 } else if (mVersion == HttpVersion::v1_1) {
246 buf.AppendLiteral("1.1 ");
247 } else {
248 buf.AppendLiteral("1.0 ");
249 }
250
251 buf.Append(nsPrintfCString("%u", unsigned(mStatus)) + " "_ns + mStatusText +
252 "\r\n"_ns);
253
254 mHeaders.Flatten(buf, false, pruneTransients);
255 }
256
FlattenNetworkOriginalHeaders(nsACString & buf)257 void nsHttpResponseHead::FlattenNetworkOriginalHeaders(nsACString& buf) {
258 RecursiveMutexAutoLock monitor(mRecursiveMutex);
259 if (mVersion == HttpVersion::v0_9) {
260 return;
261 }
262
263 mHeaders.FlattenOriginalHeader(buf);
264 }
265
ParseCachedHead(const char * block)266 nsresult nsHttpResponseHead::ParseCachedHead(const char* block) {
267 RecursiveMutexAutoLock monitor(mRecursiveMutex);
268 LOG(("nsHttpResponseHead::ParseCachedHead [this=%p]\n", this));
269
270 // this command works on a buffer as prepared by Flatten, as such it is
271 // not very forgiving ;-)
272
273 char* p = PL_strstr(block, "\r\n");
274 if (!p) return NS_ERROR_UNEXPECTED;
275
276 ParseStatusLine_locked(nsDependentCSubstring(block, p - block));
277
278 do {
279 block = p + 2;
280
281 if (*block == 0) break;
282
283 p = PL_strstr(block, "\r\n");
284 if (!p) return NS_ERROR_UNEXPECTED;
285
286 Unused << ParseHeaderLine_locked(nsDependentCSubstring(block, p - block),
287 false);
288
289 } while (true);
290
291 return NS_OK;
292 }
293
ParseCachedOriginalHeaders(char * block)294 nsresult nsHttpResponseHead::ParseCachedOriginalHeaders(char* block) {
295 RecursiveMutexAutoLock monitor(mRecursiveMutex);
296 LOG(("nsHttpResponseHead::ParseCachedOriginalHeader [this=%p]\n", this));
297
298 // this command works on a buffer as prepared by FlattenOriginalHeader,
299 // as such it is not very forgiving ;-)
300
301 if (!block) {
302 return NS_ERROR_UNEXPECTED;
303 }
304
305 char* p = block;
306 nsHttpAtom hdr;
307 nsAutoCString headerNameOriginal;
308 nsAutoCString val;
309 nsresult rv;
310
311 do {
312 block = p;
313
314 if (*block == 0) break;
315
316 p = PL_strstr(block, "\r\n");
317 if (!p) return NS_ERROR_UNEXPECTED;
318
319 *p = 0;
320 if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
321 nsDependentCString(block, p - block), &hdr, &headerNameOriginal,
322 &val))) {
323 return NS_OK;
324 }
325
326 rv = mHeaders.SetResponseHeaderFromCache(
327 hdr, headerNameOriginal, val,
328 nsHttpHeaderArray::eVarietyResponseNetOriginal);
329
330 if (NS_FAILED(rv)) {
331 return rv;
332 }
333
334 p = p + 2;
335 } while (true);
336
337 return NS_OK;
338 }
339
AssignDefaultStatusText()340 void nsHttpResponseHead::AssignDefaultStatusText() {
341 LOG(("response status line needs default reason phrase\n"));
342
343 // if a http response doesn't contain a reason phrase, put one in based
344 // on the status code. The reason phrase is totally meaningless so its
345 // ok to have a default catch all here - but this makes debuggers and addons
346 // a little saner to use if we don't map things to "404 OK" or other nonsense.
347 // In particular, HTTP/2 does not use reason phrases at all so they need to
348 // always be injected.
349
350 switch (mStatus) {
351 // start with the most common
352 case 200:
353 mStatusText.AssignLiteral("OK");
354 break;
355 case 404:
356 mStatusText.AssignLiteral("Not Found");
357 break;
358 case 301:
359 mStatusText.AssignLiteral("Moved Permanently");
360 break;
361 case 304:
362 mStatusText.AssignLiteral("Not Modified");
363 break;
364 case 307:
365 mStatusText.AssignLiteral("Temporary Redirect");
366 break;
367 case 500:
368 mStatusText.AssignLiteral("Internal Server Error");
369 break;
370
371 // also well known
372 case 100:
373 mStatusText.AssignLiteral("Continue");
374 break;
375 case 101:
376 mStatusText.AssignLiteral("Switching Protocols");
377 break;
378 case 201:
379 mStatusText.AssignLiteral("Created");
380 break;
381 case 202:
382 mStatusText.AssignLiteral("Accepted");
383 break;
384 case 203:
385 mStatusText.AssignLiteral("Non Authoritative");
386 break;
387 case 204:
388 mStatusText.AssignLiteral("No Content");
389 break;
390 case 205:
391 mStatusText.AssignLiteral("Reset Content");
392 break;
393 case 206:
394 mStatusText.AssignLiteral("Partial Content");
395 break;
396 case 207:
397 mStatusText.AssignLiteral("Multi-Status");
398 break;
399 case 208:
400 mStatusText.AssignLiteral("Already Reported");
401 break;
402 case 300:
403 mStatusText.AssignLiteral("Multiple Choices");
404 break;
405 case 302:
406 mStatusText.AssignLiteral("Found");
407 break;
408 case 303:
409 mStatusText.AssignLiteral("See Other");
410 break;
411 case 305:
412 mStatusText.AssignLiteral("Use Proxy");
413 break;
414 case 308:
415 mStatusText.AssignLiteral("Permanent Redirect");
416 break;
417 case 400:
418 mStatusText.AssignLiteral("Bad Request");
419 break;
420 case 401:
421 mStatusText.AssignLiteral("Unauthorized");
422 break;
423 case 402:
424 mStatusText.AssignLiteral("Payment Required");
425 break;
426 case 403:
427 mStatusText.AssignLiteral("Forbidden");
428 break;
429 case 405:
430 mStatusText.AssignLiteral("Method Not Allowed");
431 break;
432 case 406:
433 mStatusText.AssignLiteral("Not Acceptable");
434 break;
435 case 407:
436 mStatusText.AssignLiteral("Proxy Authentication Required");
437 break;
438 case 408:
439 mStatusText.AssignLiteral("Request Timeout");
440 break;
441 case 409:
442 mStatusText.AssignLiteral("Conflict");
443 break;
444 case 410:
445 mStatusText.AssignLiteral("Gone");
446 break;
447 case 411:
448 mStatusText.AssignLiteral("Length Required");
449 break;
450 case 412:
451 mStatusText.AssignLiteral("Precondition Failed");
452 break;
453 case 413:
454 mStatusText.AssignLiteral("Request Entity Too Large");
455 break;
456 case 414:
457 mStatusText.AssignLiteral("Request URI Too Long");
458 break;
459 case 415:
460 mStatusText.AssignLiteral("Unsupported Media Type");
461 break;
462 case 416:
463 mStatusText.AssignLiteral("Requested Range Not Satisfiable");
464 break;
465 case 417:
466 mStatusText.AssignLiteral("Expectation Failed");
467 break;
468 case 418:
469 mStatusText.AssignLiteral("I'm a teapot");
470 break;
471 case 421:
472 mStatusText.AssignLiteral("Misdirected Request");
473 break;
474 case 422:
475 mStatusText.AssignLiteral("Unprocessable Entity");
476 break;
477 case 423:
478 mStatusText.AssignLiteral("Locked");
479 break;
480 case 424:
481 mStatusText.AssignLiteral("Failed Dependency");
482 break;
483 case 425:
484 mStatusText.AssignLiteral("Too Early");
485 break;
486 case 426:
487 mStatusText.AssignLiteral("Upgrade Required");
488 break;
489 case 428:
490 mStatusText.AssignLiteral("Precondition Required");
491 break;
492 case 429:
493 mStatusText.AssignLiteral("Too Many Requests");
494 break;
495 case 431:
496 mStatusText.AssignLiteral("Request Header Fields Too Large");
497 break;
498 case 451:
499 mStatusText.AssignLiteral("Unavailable For Legal Reasons");
500 break;
501 case 501:
502 mStatusText.AssignLiteral("Not Implemented");
503 break;
504 case 502:
505 mStatusText.AssignLiteral("Bad Gateway");
506 break;
507 case 503:
508 mStatusText.AssignLiteral("Service Unavailable");
509 break;
510 case 504:
511 mStatusText.AssignLiteral("Gateway Timeout");
512 break;
513 case 505:
514 mStatusText.AssignLiteral("HTTP Version Unsupported");
515 break;
516 case 506:
517 mStatusText.AssignLiteral("Variant Also Negotiates");
518 break;
519 case 507:
520 mStatusText.AssignLiteral("Insufficient Storage ");
521 break;
522 case 508:
523 mStatusText.AssignLiteral("Loop Detected");
524 break;
525 case 510:
526 mStatusText.AssignLiteral("Not Extended");
527 break;
528 case 511:
529 mStatusText.AssignLiteral("Network Authentication Required");
530 break;
531 default:
532 mStatusText.AssignLiteral("No Reason Phrase");
533 break;
534 }
535 }
536
ParseStatusLine(const nsACString & line)537 void nsHttpResponseHead::ParseStatusLine(const nsACString& line) {
538 RecursiveMutexAutoLock monitor(mRecursiveMutex);
539 ParseStatusLine_locked(line);
540 }
541
ParseStatusLine_locked(const nsACString & line)542 void nsHttpResponseHead::ParseStatusLine_locked(const nsACString& line) {
543 //
544 // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
545 //
546
547 const char* start = line.BeginReading();
548 const char* end = line.EndReading();
549 const char* p = start;
550
551 // HTTP-Version
552 ParseVersion(start);
553
554 int32_t index = line.FindChar(' ');
555
556 if ((mVersion == HttpVersion::v0_9) || (index == -1)) {
557 mStatus = 200;
558 AssignDefaultStatusText();
559 } else {
560 // Status-Code
561 p += index + 1;
562 mStatus = (uint16_t)atoi(p);
563 if (mStatus == 0) {
564 LOG(("mal-formed response status; assuming status = 200\n"));
565 mStatus = 200;
566 }
567
568 // Reason-Phrase is whatever is remaining of the line
569 index = line.FindChar(' ', p - start);
570 if (index == -1) {
571 AssignDefaultStatusText();
572 } else {
573 p = start + index + 1;
574 mStatusText = nsDependentCSubstring(p, end - p);
575 }
576 }
577
578 LOG1(("Have status line [version=%u status=%u statusText=%s]\n",
579 unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
580 }
581
ParseHeaderLine(const nsACString & line)582 nsresult nsHttpResponseHead::ParseHeaderLine(const nsACString& line) {
583 RecursiveMutexAutoLock monitor(mRecursiveMutex);
584 return ParseHeaderLine_locked(line, true);
585 }
586
ParseHeaderLine_locked(const nsACString & line,bool originalFromNetHeaders)587 nsresult nsHttpResponseHead::ParseHeaderLine_locked(
588 const nsACString& line, bool originalFromNetHeaders) {
589 nsHttpAtom hdr;
590 nsAutoCString headerNameOriginal;
591 nsAutoCString val;
592
593 if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
594 line, &hdr, &headerNameOriginal, &val))) {
595 return NS_OK;
596 }
597 nsresult rv;
598 if (originalFromNetHeaders) {
599 rv = mHeaders.SetHeaderFromNet(hdr, headerNameOriginal, val, true);
600 } else {
601 rv = mHeaders.SetResponseHeaderFromCache(
602 hdr, headerNameOriginal, val, nsHttpHeaderArray::eVarietyResponse);
603 }
604 if (NS_FAILED(rv)) {
605 return rv;
606 }
607
608 // leading and trailing LWS has been removed from |val|
609
610 // handle some special case headers...
611 if (hdr == nsHttp::Content_Length) {
612 int64_t len;
613 const char* ignored;
614 // permit only a single value here.
615 if (nsHttp::ParseInt64(val.get(), &ignored, &len)) {
616 mContentLength = len;
617 } else {
618 // If this is a negative content length then just ignore it
619 LOG(("invalid content-length! %s\n", val.get()));
620 }
621 } else if (hdr == nsHttp::Content_Type) {
622 LOG(("ParseContentType [type=%s]\n", val.get()));
623 bool dummy;
624 net_ParseContentType(val, mContentType, mContentCharset, &dummy);
625 } else if (hdr == nsHttp::Cache_Control) {
626 ParseCacheControl(val.get());
627 } else if (hdr == nsHttp::Pragma) {
628 ParsePragma(val.get());
629 }
630 return NS_OK;
631 }
632
633 // From section 13.2.3 of RFC2616, we compute the current age of a cached
634 // response as follows:
635 //
636 // currentAge = max(max(0, responseTime - dateValue), ageValue)
637 // + now - requestTime
638 //
639 // where responseTime == now
640 //
641 // This is typically a very small number.
642 //
ComputeCurrentAge(uint32_t now,uint32_t requestTime,uint32_t * result)643 nsresult nsHttpResponseHead::ComputeCurrentAge(uint32_t now,
644 uint32_t requestTime,
645 uint32_t* result) {
646 RecursiveMutexAutoLock monitor(mRecursiveMutex);
647 uint32_t dateValue;
648 uint32_t ageValue;
649
650 *result = 0;
651
652 if (requestTime > now) {
653 // for calculation purposes lets not allow the request to happen in the
654 // future
655 requestTime = now;
656 }
657
658 if (NS_FAILED(GetDateValue_locked(&dateValue))) {
659 LOG(
660 ("nsHttpResponseHead::ComputeCurrentAge [this=%p] "
661 "Date response header not set!\n",
662 this));
663 // Assume we have a fast connection and that our clock
664 // is in sync with the server.
665 dateValue = now;
666 }
667
668 // Compute apparent age
669 if (now > dateValue) *result = now - dateValue;
670
671 // Compute corrected received age
672 if (NS_SUCCEEDED(GetAgeValue_locked(&ageValue))) {
673 *result = std::max(*result, ageValue);
674 }
675
676 // Compute current age
677 *result += (now - requestTime);
678 return NS_OK;
679 }
680
681 // From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
682 // response as follows:
683 //
684 // freshnessLifetime = max_age_value
685 // <or>
686 // freshnessLifetime = expires_value - date_value
687 // <or>
688 // freshnessLifetime = min(one-week,
689 // (date_value - last_modified_value) * 0.10)
690 // <or>
691 // freshnessLifetime = 0
692 //
ComputeFreshnessLifetime(uint32_t * result)693 nsresult nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t* result) {
694 RecursiveMutexAutoLock monitor(mRecursiveMutex);
695 *result = 0;
696
697 // Try HTTP/1.1 style max-age directive...
698 if (NS_SUCCEEDED(GetMaxAgeValue_locked(result))) return NS_OK;
699
700 *result = 0;
701
702 uint32_t date = 0, date2 = 0;
703 if (NS_FAILED(GetDateValue_locked(&date))) {
704 date = NowInSeconds(); // synthesize a date header if none exists
705 }
706
707 // Try HTTP/1.0 style expires header...
708 if (NS_SUCCEEDED(GetExpiresValue_locked(&date2))) {
709 if (date2 > date) *result = date2 - date;
710 // the Expires header can specify a date in the past.
711 return NS_OK;
712 }
713
714 // These responses can be cached indefinitely.
715 if ((mStatus == 300) || (mStatus == 410) ||
716 nsHttp::IsPermanentRedirect(mStatus)) {
717 LOG(
718 ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
719 "Assign an infinite heuristic lifetime\n",
720 this));
721 *result = uint32_t(-1);
722 return NS_OK;
723 }
724
725 if (mStatus >= 400) {
726 LOG(
727 ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
728 "Do not calculate heuristic max-age for most responses >= 400\n",
729 this));
730 return NS_OK;
731 }
732
733 // From RFC 7234 Section 4.2.2, heuristics can only be used on responses
734 // without explicit freshness whose status codes are defined as cacheable
735 // by default, and those responses without explicit freshness that have been
736 // marked as explicitly cacheable.
737 // Note that |MustValidate| handled most of non-cacheable status codes.
738 if ((mStatus == 302 || mStatus == 304 || mStatus == 307) &&
739 !mCacheControlPublic && !mCacheControlPrivate) {
740 LOG((
741 "nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
742 "Do not calculate heuristic max-age for non-cacheable status code %u\n",
743 this, unsigned(mStatus)));
744 return NS_OK;
745 }
746
747 // Fallback on heuristic using last modified header...
748 if (NS_SUCCEEDED(GetLastModifiedValue_locked(&date2))) {
749 LOG(("using last-modified to determine freshness-lifetime\n"));
750 LOG(("last-modified = %u, date = %u\n", date2, date));
751 if (date2 <= date) {
752 // this only makes sense if last-modified is actually in the past
753 *result = (date - date2) / 10;
754 const uint32_t kOneWeek = 60 * 60 * 24 * 7;
755 *result = std::min(kOneWeek, *result);
756 return NS_OK;
757 }
758 }
759
760 LOG(
761 ("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
762 "Insufficient information to compute a non-zero freshness "
763 "lifetime!\n",
764 this));
765
766 return NS_OK;
767 }
768
MustValidate()769 bool nsHttpResponseHead::MustValidate() {
770 RecursiveMutexAutoLock monitor(mRecursiveMutex);
771 LOG(("nsHttpResponseHead::MustValidate ??\n"));
772
773 // Some response codes are cacheable, but the rest are not. This switch should
774 // stay in sync with the list in nsHttpChannel::ContinueProcessResponse3
775 switch (mStatus) {
776 // Success codes
777 case 200:
778 case 203:
779 case 204:
780 case 206:
781 // Cacheable redirects
782 case 300:
783 case 301:
784 case 302:
785 case 304:
786 case 307:
787 case 308:
788 // Gone forever
789 case 410:
790 break;
791 // Uncacheable redirects
792 case 303:
793 case 305:
794 // Other known errors
795 case 401:
796 case 407:
797 case 412:
798 case 416:
799 case 425:
800 case 429:
801 default: // revalidate unknown error pages
802 LOG(("Must validate since response is an uncacheable error page\n"));
803 return true;
804 }
805
806 // The no-cache response header indicates that we must validate this
807 // cached response before reusing.
808 if (NoCache_locked()) {
809 LOG(("Must validate since response contains 'no-cache' header\n"));
810 return true;
811 }
812
813 // Likewise, if the response is no-store, then we must validate this
814 // cached response before reusing. NOTE: it may seem odd that a no-store
815 // response may be cached, but indeed all responses are cached in order
816 // to support File->SaveAs, View->PageSource, and other browser features.
817 if (mCacheControlNoStore) {
818 LOG(("Must validate since response contains 'no-store' header\n"));
819 return true;
820 }
821
822 // Compare the Expires header to the Date header. If the server sent an
823 // Expires header with a timestamp in the past, then we must validate this
824 // cached response before reusing.
825 if (ExpiresInPast_locked()) {
826 LOG(("Must validate since Expires < Date\n"));
827 return true;
828 }
829
830 LOG(("no mandatory validation requirement\n"));
831 return false;
832 }
833
MustValidateIfExpired()834 bool nsHttpResponseHead::MustValidateIfExpired() {
835 // according to RFC2616, section 14.9.4:
836 //
837 // When the must-revalidate directive is present in a response received by a
838 // cache, that cache MUST NOT use the entry after it becomes stale to respond
839 // to a subsequent request without first revalidating it with the origin
840 // server.
841 //
842 return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate");
843 }
844
StaleWhileRevalidate(uint32_t now,uint32_t expiration)845 bool nsHttpResponseHead::StaleWhileRevalidate(uint32_t now,
846 uint32_t expiration) {
847 RecursiveMutexAutoLock monitor(mRecursiveMutex);
848
849 if (expiration <= 0 || !mCacheControlStaleWhileRevalidateSet) {
850 return false;
851 }
852
853 // 'expiration' is the expiration time (an absolute unit), the swr window
854 // extends the expiration time.
855 CheckedInt<uint32_t> stallValidUntil = expiration;
856 stallValidUntil += mCacheControlStaleWhileRevalidate;
857 if (!stallValidUntil.isValid()) {
858 // overflow means an indefinite stale window
859 return true;
860 }
861
862 return now <= stallValidUntil.value();
863 }
864
IsResumable()865 bool nsHttpResponseHead::IsResumable() {
866 RecursiveMutexAutoLock monitor(mRecursiveMutex);
867 // even though some HTTP/1.0 servers may support byte range requests, we're
868 // not going to bother with them, since those servers wouldn't understand
869 // If-Range. Also, while in theory it may be possible to resume when the
870 // status code is not 200, it is unlikely to be worth the trouble, especially
871 // for non-2xx responses.
872 return mStatus == 200 && mVersion >= HttpVersion::v1_1 &&
873 mHeaders.PeekHeader(nsHttp::Content_Length) &&
874 (mHeaders.PeekHeader(nsHttp::ETag) ||
875 mHeaders.PeekHeader(nsHttp::Last_Modified)) &&
876 mHeaders.HasHeaderValue(nsHttp::Accept_Ranges, "bytes");
877 }
878
ExpiresInPast()879 bool nsHttpResponseHead::ExpiresInPast() {
880 RecursiveMutexAutoLock monitor(mRecursiveMutex);
881 return ExpiresInPast_locked();
882 }
883
ExpiresInPast_locked() const884 bool nsHttpResponseHead::ExpiresInPast_locked() const {
885 uint32_t maxAgeVal, expiresVal, dateVal;
886
887 // Bug #203271. Ensure max-age directive takes precedence over Expires
888 if (NS_SUCCEEDED(GetMaxAgeValue_locked(&maxAgeVal))) {
889 return false;
890 }
891
892 return NS_SUCCEEDED(GetExpiresValue_locked(&expiresVal)) &&
893 NS_SUCCEEDED(GetDateValue_locked(&dateVal)) && expiresVal < dateVal;
894 }
895
UpdateHeaders(nsHttpResponseHead * aOther)896 void nsHttpResponseHead::UpdateHeaders(nsHttpResponseHead* aOther) {
897 LOG(("nsHttpResponseHead::UpdateHeaders [this=%p]\n", this));
898
899 RecursiveMutexAutoLock monitor(mRecursiveMutex);
900 RecursiveMutexAutoLock monitorOther(aOther->mRecursiveMutex);
901
902 uint32_t i, count = aOther->mHeaders.Count();
903 for (i = 0; i < count; ++i) {
904 nsHttpAtom header;
905 nsAutoCString headerNameOriginal;
906 const char* val =
907 aOther->mHeaders.PeekHeaderAt(i, header, headerNameOriginal);
908
909 if (!val) {
910 continue;
911 }
912
913 // Ignore any hop-by-hop headers...
914 if (header == nsHttp::Connection || header == nsHttp::Proxy_Connection ||
915 header == nsHttp::Keep_Alive || header == nsHttp::Proxy_Authenticate ||
916 header == nsHttp::Proxy_Authorization || // not a response header!
917 header == nsHttp::TE || header == nsHttp::Trailer ||
918 header == nsHttp::Transfer_Encoding || header == nsHttp::Upgrade ||
919 // Ignore any non-modifiable headers...
920 header == nsHttp::Content_Location || header == nsHttp::Content_MD5 ||
921 header == nsHttp::ETag ||
922 // Assume Cache-Control: "no-transform"
923 header == nsHttp::Content_Encoding || header == nsHttp::Content_Range ||
924 header == nsHttp::Content_Type ||
925 // Ignore wacky headers too...
926 // this one is for MS servers that send "Content-Length: 0"
927 // on 304 responses
928 header == nsHttp::Content_Length) {
929 LOG(("ignoring response header [%s: %s]\n", header.get(), val));
930 } else {
931 LOG(("new response header [%s: %s]\n", header.get(), val));
932
933 // overwrite the current header value with the new value...
934 DebugOnly<nsresult> rv =
935 SetHeader_locked(header, headerNameOriginal, nsDependentCString(val));
936 MOZ_ASSERT(NS_SUCCEEDED(rv));
937 }
938 }
939 }
940
Reset()941 void nsHttpResponseHead::Reset() {
942 LOG(("nsHttpResponseHead::Reset\n"));
943
944 RecursiveMutexAutoLock monitor(mRecursiveMutex);
945
946 mHeaders.Clear();
947
948 mVersion = HttpVersion::v1_1;
949 mStatus = 200;
950 mContentLength = -1;
951 mHasCacheControl = false;
952 mCacheControlPublic = false;
953 mCacheControlPrivate = false;
954 mCacheControlNoStore = false;
955 mCacheControlNoCache = false;
956 mCacheControlImmutable = false;
957 mCacheControlStaleWhileRevalidateSet = false;
958 mCacheControlStaleWhileRevalidate = 0;
959 mCacheControlMaxAgeSet = false;
960 mCacheControlMaxAge = 0;
961 mPragmaNoCache = false;
962 mStatusText.Truncate();
963 mContentType.Truncate();
964 mContentCharset.Truncate();
965 }
966
ParseDateHeader(const nsHttpAtom & header,uint32_t * result) const967 nsresult nsHttpResponseHead::ParseDateHeader(const nsHttpAtom& header,
968 uint32_t* result) const {
969 const char* val = mHeaders.PeekHeader(header);
970 if (!val) return NS_ERROR_NOT_AVAILABLE;
971
972 PRTime time;
973 PRStatus st = PR_ParseTimeString(val, true, &time);
974 if (st != PR_SUCCESS) return NS_ERROR_NOT_AVAILABLE;
975
976 *result = PRTimeToSeconds(time);
977 return NS_OK;
978 }
979
GetAgeValue(uint32_t * result)980 nsresult nsHttpResponseHead::GetAgeValue(uint32_t* result) {
981 RecursiveMutexAutoLock monitor(mRecursiveMutex);
982 return GetAgeValue_locked(result);
983 }
984
GetAgeValue_locked(uint32_t * result) const985 nsresult nsHttpResponseHead::GetAgeValue_locked(uint32_t* result) const {
986 const char* val = mHeaders.PeekHeader(nsHttp::Age);
987 if (!val) return NS_ERROR_NOT_AVAILABLE;
988
989 *result = (uint32_t)atoi(val);
990 return NS_OK;
991 }
992
993 // Return the value of the (HTTP 1.1) max-age directive, which itself is a
994 // component of the Cache-Control response header
GetMaxAgeValue(uint32_t * result)995 nsresult nsHttpResponseHead::GetMaxAgeValue(uint32_t* result) {
996 RecursiveMutexAutoLock monitor(mRecursiveMutex);
997 return GetMaxAgeValue_locked(result);
998 }
999
GetMaxAgeValue_locked(uint32_t * result) const1000 nsresult nsHttpResponseHead::GetMaxAgeValue_locked(uint32_t* result) const {
1001 if (!mCacheControlMaxAgeSet) {
1002 return NS_ERROR_NOT_AVAILABLE;
1003 }
1004
1005 *result = mCacheControlMaxAge;
1006 return NS_OK;
1007 }
1008
GetDateValue(uint32_t * result)1009 nsresult nsHttpResponseHead::GetDateValue(uint32_t* result) {
1010 RecursiveMutexAutoLock monitor(mRecursiveMutex);
1011 return GetDateValue_locked(result);
1012 }
1013
GetExpiresValue(uint32_t * result)1014 nsresult nsHttpResponseHead::GetExpiresValue(uint32_t* result) {
1015 RecursiveMutexAutoLock monitor(mRecursiveMutex);
1016 return GetExpiresValue_locked(result);
1017 }
1018
GetExpiresValue_locked(uint32_t * result) const1019 nsresult nsHttpResponseHead::GetExpiresValue_locked(uint32_t* result) const {
1020 const char* val = mHeaders.PeekHeader(nsHttp::Expires);
1021 if (!val) return NS_ERROR_NOT_AVAILABLE;
1022
1023 PRTime time;
1024 PRStatus st = PR_ParseTimeString(val, true, &time);
1025 if (st != PR_SUCCESS) {
1026 // parsing failed... RFC 2616 section 14.21 says we should treat this
1027 // as an expiration time in the past.
1028 *result = 0;
1029 return NS_OK;
1030 }
1031
1032 if (time < 0) {
1033 *result = 0;
1034 } else {
1035 *result = PRTimeToSeconds(time);
1036 }
1037 return NS_OK;
1038 }
1039
GetLastModifiedValue(uint32_t * result)1040 nsresult nsHttpResponseHead::GetLastModifiedValue(uint32_t* result) {
1041 RecursiveMutexAutoLock monitor(mRecursiveMutex);
1042 return ParseDateHeader(nsHttp::Last_Modified, result);
1043 }
1044
operator ==(const nsHttpResponseHead & aOther) const1045 bool nsHttpResponseHead::operator==(const nsHttpResponseHead& aOther) const {
1046 nsHttpResponseHead& curr = const_cast<nsHttpResponseHead&>(*this);
1047 nsHttpResponseHead& other = const_cast<nsHttpResponseHead&>(aOther);
1048 RecursiveMutexAutoLock monitorOther(other.mRecursiveMutex);
1049 RecursiveMutexAutoLock monitor(curr.mRecursiveMutex);
1050
1051 return mHeaders == aOther.mHeaders && mVersion == aOther.mVersion &&
1052 mStatus == aOther.mStatus && mStatusText == aOther.mStatusText &&
1053 mContentLength == aOther.mContentLength &&
1054 mContentType == aOther.mContentType &&
1055 mContentCharset == aOther.mContentCharset &&
1056 mHasCacheControl == aOther.mHasCacheControl &&
1057 mCacheControlPublic == aOther.mCacheControlPublic &&
1058 mCacheControlPrivate == aOther.mCacheControlPrivate &&
1059 mCacheControlNoCache == aOther.mCacheControlNoCache &&
1060 mCacheControlNoStore == aOther.mCacheControlNoStore &&
1061 mCacheControlImmutable == aOther.mCacheControlImmutable &&
1062 mCacheControlStaleWhileRevalidateSet ==
1063 aOther.mCacheControlStaleWhileRevalidateSet &&
1064 mCacheControlStaleWhileRevalidate ==
1065 aOther.mCacheControlStaleWhileRevalidate &&
1066 mCacheControlMaxAgeSet == aOther.mCacheControlMaxAgeSet &&
1067 mCacheControlMaxAge == aOther.mCacheControlMaxAge &&
1068 mPragmaNoCache == aOther.mPragmaNoCache;
1069 }
1070
TotalEntitySize()1071 int64_t nsHttpResponseHead::TotalEntitySize() {
1072 RecursiveMutexAutoLock monitor(mRecursiveMutex);
1073 const char* contentRange = mHeaders.PeekHeader(nsHttp::Content_Range);
1074 if (!contentRange) return mContentLength;
1075
1076 // Total length is after a slash
1077 const char* slash = strrchr(contentRange, '/');
1078 if (!slash) return -1; // No idea what the length is
1079
1080 slash++;
1081 if (*slash == '*') { // Server doesn't know the length
1082 return -1;
1083 }
1084
1085 int64_t size;
1086 if (!nsHttp::ParseInt64(slash, &size)) size = UINT64_MAX;
1087 return size;
1088 }
1089
1090 //-----------------------------------------------------------------------------
1091 // nsHttpResponseHead <private>
1092 //-----------------------------------------------------------------------------
1093
ParseVersion(const char * str)1094 void nsHttpResponseHead::ParseVersion(const char* str) {
1095 // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
1096
1097 LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str));
1098
1099 Tokenizer t(str, nullptr, "");
1100 // make sure we have HTTP at the beginning
1101 if (!t.CheckWord("HTTP")) {
1102 if (nsCRT::strncasecmp(str, "ICY ", 4) == 0) {
1103 // ShoutCast ICY is HTTP/1.0-like. Assume it is HTTP/1.0.
1104 LOG(("Treating ICY as HTTP 1.0\n"));
1105 mVersion = HttpVersion::v1_0;
1106 return;
1107 }
1108 LOG(("looks like a HTTP/0.9 response\n"));
1109 mVersion = HttpVersion::v0_9;
1110 return;
1111 }
1112
1113 if (!t.CheckChar('/')) {
1114 LOG(("server did not send a version number; assuming HTTP/1.0\n"));
1115 // NCSA/1.5.2 has a bug in which it fails to send a version number
1116 // if the request version is HTTP/1.1, so we fall back on HTTP/1.0
1117 mVersion = HttpVersion::v1_0;
1118 return;
1119 }
1120
1121 uint32_t major;
1122 if (!t.ReadInteger(&major)) {
1123 LOG(("server did not send a correct version number; assuming HTTP/1.0"));
1124 mVersion = HttpVersion::v1_0;
1125 return;
1126 }
1127
1128 if (major == 3) {
1129 mVersion = HttpVersion::v3_0;
1130 return;
1131 }
1132
1133 if (major == 2) {
1134 mVersion = HttpVersion::v2_0;
1135 return;
1136 }
1137
1138 if (major != 1) {
1139 LOG(("server did not send a correct version number; assuming HTTP/1.0"));
1140 mVersion = HttpVersion::v1_0;
1141 return;
1142 }
1143
1144 if (!t.CheckChar('.')) {
1145 LOG(("mal-formed server version; assuming HTTP/1.0\n"));
1146 mVersion = HttpVersion::v1_0;
1147 return;
1148 }
1149
1150 uint32_t minor;
1151 if (!t.ReadInteger(&minor)) {
1152 LOG(("server did not send a correct version number; assuming HTTP/1.0"));
1153 mVersion = HttpVersion::v1_0;
1154 return;
1155 }
1156
1157 if (minor >= 1) {
1158 // at least HTTP/1.1
1159 mVersion = HttpVersion::v1_1;
1160 } else {
1161 // treat anything else as version 1.0
1162 mVersion = HttpVersion::v1_0;
1163 }
1164 }
1165
ParseCacheControl(const char * val)1166 void nsHttpResponseHead::ParseCacheControl(const char* val) {
1167 if (!(val && *val)) {
1168 // clear flags
1169 mHasCacheControl = false;
1170 mCacheControlPublic = false;
1171 mCacheControlPrivate = false;
1172 mCacheControlNoCache = false;
1173 mCacheControlNoStore = false;
1174 mCacheControlImmutable = false;
1175 mCacheControlStaleWhileRevalidateSet = false;
1176 mCacheControlStaleWhileRevalidate = 0;
1177 mCacheControlMaxAgeSet = false;
1178 mCacheControlMaxAge = 0;
1179 return;
1180 }
1181
1182 nsDependentCString cacheControlRequestHeader(val);
1183 CacheControlParser cacheControlRequest(cacheControlRequestHeader);
1184
1185 mHasCacheControl = true;
1186 mCacheControlPublic = cacheControlRequest.Public();
1187 mCacheControlPrivate = cacheControlRequest.Private();
1188 mCacheControlNoCache = cacheControlRequest.NoCache();
1189 mCacheControlNoStore = cacheControlRequest.NoStore();
1190 mCacheControlImmutable = cacheControlRequest.Immutable();
1191 mCacheControlStaleWhileRevalidateSet =
1192 cacheControlRequest.StaleWhileRevalidate(
1193 &mCacheControlStaleWhileRevalidate);
1194 mCacheControlMaxAgeSet = cacheControlRequest.MaxAge(&mCacheControlMaxAge);
1195 }
1196
ParsePragma(const char * val)1197 void nsHttpResponseHead::ParsePragma(const char* val) {
1198 LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val));
1199
1200 if (!(val && *val)) {
1201 // clear no-cache flag
1202 mPragmaNoCache = false;
1203 return;
1204 }
1205
1206 // Although 'Pragma: no-cache' is not a standard HTTP response header (it's a
1207 // request header), caching is inhibited when this header is present so as to
1208 // match existing Navigator behavior.
1209 mPragmaNoCache = nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS);
1210 }
1211
VisitHeaders(nsIHttpHeaderVisitor * visitor,nsHttpHeaderArray::VisitorFilter filter)1212 nsresult nsHttpResponseHead::VisitHeaders(
1213 nsIHttpHeaderVisitor* visitor, nsHttpHeaderArray::VisitorFilter filter) {
1214 RecursiveMutexAutoLock monitor(mRecursiveMutex);
1215 mInVisitHeaders = true;
1216 nsresult rv = mHeaders.VisitHeaders(visitor, filter);
1217 mInVisitHeaders = false;
1218 return rv;
1219 }
1220
GetOriginalHeader(const nsHttpAtom & aHeader,nsIHttpHeaderVisitor * aVisitor)1221 nsresult nsHttpResponseHead::GetOriginalHeader(const nsHttpAtom& aHeader,
1222 nsIHttpHeaderVisitor* aVisitor) {
1223 RecursiveMutexAutoLock monitor(mRecursiveMutex);
1224 mInVisitHeaders = true;
1225 nsresult rv = mHeaders.GetOriginalHeader(aHeader, aVisitor);
1226 mInVisitHeaders = false;
1227 return rv;
1228 }
1229
HasContentType() const1230 bool nsHttpResponseHead::HasContentType() const {
1231 RecursiveMutexAutoLock monitor(mRecursiveMutex);
1232 return !mContentType.IsEmpty();
1233 }
1234
HasContentCharset()1235 bool nsHttpResponseHead::HasContentCharset() {
1236 RecursiveMutexAutoLock monitor(mRecursiveMutex);
1237 return !mContentCharset.IsEmpty();
1238 }
1239
GetContentTypeOptionsHeader(nsACString & aOutput)1240 bool nsHttpResponseHead::GetContentTypeOptionsHeader(nsACString& aOutput) {
1241 aOutput.Truncate();
1242
1243 nsAutoCString contentTypeOptionsHeader;
1244 Unused << GetHeader(nsHttp::X_Content_Type_Options, contentTypeOptionsHeader);
1245 if (contentTypeOptionsHeader.IsEmpty()) {
1246 // if there is no XCTO header, then there is nothing to do.
1247 return false;
1248 }
1249
1250 // XCTO header might contain multiple values which are comma separated, so:
1251 // a) let's skip all subsequent values
1252 // e.g. " NoSniFF , foo " will be " NoSniFF "
1253 int32_t idx = contentTypeOptionsHeader.Find(",");
1254 if (idx > 0) {
1255 contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
1256 }
1257 // b) let's trim all surrounding whitespace
1258 // e.g. " NoSniFF " -> "NoSniFF"
1259 nsHttp::TrimHTTPWhitespace(contentTypeOptionsHeader,
1260 contentTypeOptionsHeader);
1261
1262 aOutput.Assign(contentTypeOptionsHeader);
1263 return true;
1264 }
1265
1266 } // namespace net
1267 } // namespace mozilla
1268