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