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