|
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 "nsPrintfCString.h" |
|
12 #include "prtime.h" |
|
13 #include "nsURLHelper.h" |
|
14 #include <algorithm> |
|
15 |
|
16 namespace mozilla { |
|
17 namespace net { |
|
18 |
|
19 //----------------------------------------------------------------------------- |
|
20 // nsHttpResponseHead <public> |
|
21 //----------------------------------------------------------------------------- |
|
22 |
|
23 nsresult |
|
24 nsHttpResponseHead::SetHeader(nsHttpAtom hdr, |
|
25 const nsACString &val, |
|
26 bool merge) |
|
27 { |
|
28 nsresult rv = mHeaders.SetHeader(hdr, val, merge); |
|
29 if (NS_FAILED(rv)) return rv; |
|
30 |
|
31 // respond to changes in these headers. we need to reparse the entire |
|
32 // header since the change may have merged in additional values. |
|
33 if (hdr == nsHttp::Cache_Control) |
|
34 ParseCacheControl(mHeaders.PeekHeader(hdr)); |
|
35 else if (hdr == nsHttp::Pragma) |
|
36 ParsePragma(mHeaders.PeekHeader(hdr)); |
|
37 |
|
38 return NS_OK; |
|
39 } |
|
40 |
|
41 void |
|
42 nsHttpResponseHead::SetContentLength(int64_t len) |
|
43 { |
|
44 mContentLength = len; |
|
45 if (len < 0) |
|
46 mHeaders.ClearHeader(nsHttp::Content_Length); |
|
47 else |
|
48 mHeaders.SetHeader(nsHttp::Content_Length, nsPrintfCString("%lld", len)); |
|
49 } |
|
50 |
|
51 void |
|
52 nsHttpResponseHead::Flatten(nsACString &buf, bool pruneTransients) |
|
53 { |
|
54 if (mVersion == NS_HTTP_VERSION_0_9) |
|
55 return; |
|
56 |
|
57 buf.AppendLiteral("HTTP/"); |
|
58 if (mVersion == NS_HTTP_VERSION_2_0) |
|
59 buf.AppendLiteral("2.0 "); |
|
60 else if (mVersion == NS_HTTP_VERSION_1_1) |
|
61 buf.AppendLiteral("1.1 "); |
|
62 else |
|
63 buf.AppendLiteral("1.0 "); |
|
64 |
|
65 buf.Append(nsPrintfCString("%u", unsigned(mStatus)) + |
|
66 NS_LITERAL_CSTRING(" ") + |
|
67 mStatusText + |
|
68 NS_LITERAL_CSTRING("\r\n")); |
|
69 |
|
70 if (!pruneTransients) { |
|
71 mHeaders.Flatten(buf, false); |
|
72 return; |
|
73 } |
|
74 |
|
75 // otherwise, we need to iterate over the headers and only flatten |
|
76 // those that are appropriate. |
|
77 uint32_t i, count = mHeaders.Count(); |
|
78 for (i=0; i<count; ++i) { |
|
79 nsHttpAtom header; |
|
80 const char *value = mHeaders.PeekHeaderAt(i, header); |
|
81 |
|
82 if (!value || header == nsHttp::Connection |
|
83 || header == nsHttp::Proxy_Connection |
|
84 || header == nsHttp::Keep_Alive |
|
85 || header == nsHttp::WWW_Authenticate |
|
86 || header == nsHttp::Proxy_Authenticate |
|
87 || header == nsHttp::Trailer |
|
88 || header == nsHttp::Transfer_Encoding |
|
89 || header == nsHttp::Upgrade |
|
90 // XXX this will cause problems when we start honoring |
|
91 // Cache-Control: no-cache="set-cookie", what to do? |
|
92 || header == nsHttp::Set_Cookie) |
|
93 continue; |
|
94 |
|
95 // otherwise, write out the "header: value\r\n" line |
|
96 buf.Append(nsDependentCString(header.get()) + |
|
97 NS_LITERAL_CSTRING(": ") + |
|
98 nsDependentCString(value) + |
|
99 NS_LITERAL_CSTRING("\r\n")); |
|
100 } |
|
101 } |
|
102 |
|
103 nsresult |
|
104 nsHttpResponseHead::Parse(char *block) |
|
105 { |
|
106 |
|
107 LOG(("nsHttpResponseHead::Parse [this=%p]\n", this)); |
|
108 |
|
109 // this command works on a buffer as prepared by Flatten, as such it is |
|
110 // not very forgiving ;-) |
|
111 |
|
112 char *p = PL_strstr(block, "\r\n"); |
|
113 if (!p) |
|
114 return NS_ERROR_UNEXPECTED; |
|
115 |
|
116 *p = 0; |
|
117 ParseStatusLine(block); |
|
118 |
|
119 do { |
|
120 block = p + 2; |
|
121 |
|
122 if (*block == 0) |
|
123 break; |
|
124 |
|
125 p = PL_strstr(block, "\r\n"); |
|
126 if (!p) |
|
127 return NS_ERROR_UNEXPECTED; |
|
128 |
|
129 *p = 0; |
|
130 ParseHeaderLine(block); |
|
131 |
|
132 } while (1); |
|
133 |
|
134 return NS_OK; |
|
135 } |
|
136 |
|
137 void |
|
138 nsHttpResponseHead::AssignDefaultStatusText() |
|
139 { |
|
140 LOG(("response status line needs default reason phrase\n")); |
|
141 |
|
142 // if a http response doesn't contain a reason phrase, put one in based |
|
143 // on the status code. The reason phrase is totally meaningless so its |
|
144 // ok to have a default catch all here - but this makes debuggers and addons |
|
145 // a little saner to use if we don't map things to "404 OK" or other nonsense. |
|
146 // In particular, HTTP/2 does not use reason phrases at all so they need to |
|
147 // always be injected. |
|
148 |
|
149 switch (mStatus) { |
|
150 // start with the most common |
|
151 case 200: |
|
152 mStatusText.AssignLiteral("OK"); |
|
153 break; |
|
154 case 404: |
|
155 mStatusText.AssignLiteral("Not Found"); |
|
156 break; |
|
157 case 301: |
|
158 mStatusText.AssignLiteral("Moved Permanently"); |
|
159 break; |
|
160 case 304: |
|
161 mStatusText.AssignLiteral("Not Modified"); |
|
162 break; |
|
163 case 307: |
|
164 mStatusText.AssignLiteral("Temporary Redirect"); |
|
165 break; |
|
166 case 500: |
|
167 mStatusText.AssignLiteral("Internal Server Error"); |
|
168 break; |
|
169 |
|
170 // also well known |
|
171 case 100: |
|
172 mStatusText.AssignLiteral("Continue"); |
|
173 break; |
|
174 case 101: |
|
175 mStatusText.AssignLiteral("Switching Protocols"); |
|
176 break; |
|
177 case 201: |
|
178 mStatusText.AssignLiteral("Created"); |
|
179 break; |
|
180 case 202: |
|
181 mStatusText.AssignLiteral("Accepted"); |
|
182 break; |
|
183 case 203: |
|
184 mStatusText.AssignLiteral("Non Authoritative"); |
|
185 break; |
|
186 case 204: |
|
187 mStatusText.AssignLiteral("No Content"); |
|
188 break; |
|
189 case 205: |
|
190 mStatusText.AssignLiteral("Reset Content"); |
|
191 break; |
|
192 case 206: |
|
193 mStatusText.AssignLiteral("Partial Content"); |
|
194 break; |
|
195 case 300: |
|
196 mStatusText.AssignLiteral("Multiple Choices"); |
|
197 break; |
|
198 case 302: |
|
199 mStatusText.AssignLiteral("Found"); |
|
200 break; |
|
201 case 303: |
|
202 mStatusText.AssignLiteral("See Other"); |
|
203 break; |
|
204 case 305: |
|
205 mStatusText.AssignLiteral("Use Proxy"); |
|
206 break; |
|
207 case 308: |
|
208 mStatusText.AssignLiteral("Permanent Redirect"); |
|
209 break; |
|
210 case 400: |
|
211 mStatusText.AssignLiteral("Bad Request"); |
|
212 break; |
|
213 case 401: |
|
214 mStatusText.AssignLiteral("Unauthorized"); |
|
215 break; |
|
216 case 402: |
|
217 mStatusText.AssignLiteral("Payment Required"); |
|
218 break; |
|
219 case 403: |
|
220 mStatusText.AssignLiteral("Forbidden"); |
|
221 break; |
|
222 case 405: |
|
223 mStatusText.AssignLiteral("Method Not Allowed"); |
|
224 break; |
|
225 case 406: |
|
226 mStatusText.AssignLiteral("Not Acceptable"); |
|
227 break; |
|
228 case 407: |
|
229 mStatusText.AssignLiteral("Proxy Authentication Required"); |
|
230 break; |
|
231 case 408: |
|
232 mStatusText.AssignLiteral("Request Timeout"); |
|
233 break; |
|
234 case 409: |
|
235 mStatusText.AssignLiteral("Conflict"); |
|
236 break; |
|
237 case 410: |
|
238 mStatusText.AssignLiteral("Gone"); |
|
239 break; |
|
240 case 411: |
|
241 mStatusText.AssignLiteral("Length Required"); |
|
242 break; |
|
243 case 412: |
|
244 mStatusText.AssignLiteral("Precondition Failed"); |
|
245 break; |
|
246 case 413: |
|
247 mStatusText.AssignLiteral("Request Entity Too Large"); |
|
248 break; |
|
249 case 414: |
|
250 mStatusText.AssignLiteral("Request URI Too Long"); |
|
251 break; |
|
252 case 415: |
|
253 mStatusText.AssignLiteral("Unsupported Media Type"); |
|
254 break; |
|
255 case 416: |
|
256 mStatusText.AssignLiteral("Requested Range Not Satisfiable"); |
|
257 break; |
|
258 case 417: |
|
259 mStatusText.AssignLiteral("Expectation Failed"); |
|
260 break; |
|
261 case 501: |
|
262 mStatusText.AssignLiteral("Not Implemented"); |
|
263 break; |
|
264 case 502: |
|
265 mStatusText.AssignLiteral("Bad Gateway"); |
|
266 break; |
|
267 case 503: |
|
268 mStatusText.AssignLiteral("Service Unavailable"); |
|
269 break; |
|
270 case 504: |
|
271 mStatusText.AssignLiteral("Gateway Timeout"); |
|
272 break; |
|
273 case 505: |
|
274 mStatusText.AssignLiteral("HTTP Version Unsupported"); |
|
275 break; |
|
276 default: |
|
277 mStatusText.AssignLiteral("No Reason Phrase"); |
|
278 break; |
|
279 } |
|
280 } |
|
281 |
|
282 void |
|
283 nsHttpResponseHead::ParseStatusLine(const char *line) |
|
284 { |
|
285 // |
|
286 // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF |
|
287 // |
|
288 |
|
289 // HTTP-Version |
|
290 ParseVersion(line); |
|
291 |
|
292 if ((mVersion == NS_HTTP_VERSION_0_9) || !(line = PL_strchr(line, ' '))) { |
|
293 mStatus = 200; |
|
294 AssignDefaultStatusText(); |
|
295 } |
|
296 else { |
|
297 // Status-Code |
|
298 mStatus = (uint16_t) atoi(++line); |
|
299 if (mStatus == 0) { |
|
300 LOG(("mal-formed response status; assuming status = 200\n")); |
|
301 mStatus = 200; |
|
302 } |
|
303 |
|
304 // Reason-Phrase is whatever is remaining of the line |
|
305 if (!(line = PL_strchr(line, ' '))) { |
|
306 AssignDefaultStatusText(); |
|
307 } |
|
308 else |
|
309 mStatusText = nsDependentCString(++line); |
|
310 } |
|
311 |
|
312 LOG(("Have status line [version=%u status=%u statusText=%s]\n", |
|
313 unsigned(mVersion), unsigned(mStatus), mStatusText.get())); |
|
314 } |
|
315 |
|
316 nsresult |
|
317 nsHttpResponseHead::ParseHeaderLine(const char *line) |
|
318 { |
|
319 nsHttpAtom hdr = {0}; |
|
320 char *val; |
|
321 nsresult rv; |
|
322 |
|
323 rv = mHeaders.ParseHeaderLine(line, &hdr, &val); |
|
324 if (NS_FAILED(rv)) |
|
325 return rv; |
|
326 |
|
327 // leading and trailing LWS has been removed from |val| |
|
328 |
|
329 // handle some special case headers... |
|
330 if (hdr == nsHttp::Content_Length) { |
|
331 int64_t len; |
|
332 const char *ignored; |
|
333 // permit only a single value here. |
|
334 if (nsHttp::ParseInt64(val, &ignored, &len)) { |
|
335 mContentLength = len; |
|
336 } |
|
337 else { |
|
338 // If this is a negative content length then just ignore it |
|
339 LOG(("invalid content-length! %s\n", val)); |
|
340 } |
|
341 } |
|
342 else if (hdr == nsHttp::Content_Type) { |
|
343 LOG(("ParseContentType [type=%s]\n", val)); |
|
344 bool dummy; |
|
345 net_ParseContentType(nsDependentCString(val), |
|
346 mContentType, mContentCharset, &dummy); |
|
347 } |
|
348 else if (hdr == nsHttp::Cache_Control) |
|
349 ParseCacheControl(val); |
|
350 else if (hdr == nsHttp::Pragma) |
|
351 ParsePragma(val); |
|
352 return NS_OK; |
|
353 } |
|
354 |
|
355 // From section 13.2.3 of RFC2616, we compute the current age of a cached |
|
356 // response as follows: |
|
357 // |
|
358 // currentAge = max(max(0, responseTime - dateValue), ageValue) |
|
359 // + now - requestTime |
|
360 // |
|
361 // where responseTime == now |
|
362 // |
|
363 // This is typically a very small number. |
|
364 // |
|
365 nsresult |
|
366 nsHttpResponseHead::ComputeCurrentAge(uint32_t now, |
|
367 uint32_t requestTime, |
|
368 uint32_t *result) const |
|
369 { |
|
370 uint32_t dateValue; |
|
371 uint32_t ageValue; |
|
372 |
|
373 *result = 0; |
|
374 |
|
375 if (NS_FAILED(GetDateValue(&dateValue))) { |
|
376 LOG(("nsHttpResponseHead::ComputeCurrentAge [this=%p] " |
|
377 "Date response header not set!\n", this)); |
|
378 // Assume we have a fast connection and that our clock |
|
379 // is in sync with the server. |
|
380 dateValue = now; |
|
381 } |
|
382 |
|
383 // Compute apparent age |
|
384 if (now > dateValue) |
|
385 *result = now - dateValue; |
|
386 |
|
387 // Compute corrected received age |
|
388 if (NS_SUCCEEDED(GetAgeValue(&ageValue))) |
|
389 *result = std::max(*result, ageValue); |
|
390 |
|
391 MOZ_ASSERT(now >= requestTime, "bogus request time"); |
|
392 |
|
393 // Compute current age |
|
394 *result += (now - requestTime); |
|
395 return NS_OK; |
|
396 } |
|
397 |
|
398 // From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached |
|
399 // response as follows: |
|
400 // |
|
401 // freshnessLifetime = max_age_value |
|
402 // <or> |
|
403 // freshnessLifetime = expires_value - date_value |
|
404 // <or> |
|
405 // freshnessLifetime = (date_value - last_modified_value) * 0.10 |
|
406 // <or> |
|
407 // freshnessLifetime = 0 |
|
408 // |
|
409 nsresult |
|
410 nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t *result) const |
|
411 { |
|
412 *result = 0; |
|
413 |
|
414 // Try HTTP/1.1 style max-age directive... |
|
415 if (NS_SUCCEEDED(GetMaxAgeValue(result))) |
|
416 return NS_OK; |
|
417 |
|
418 *result = 0; |
|
419 |
|
420 uint32_t date = 0, date2 = 0; |
|
421 if (NS_FAILED(GetDateValue(&date))) |
|
422 date = NowInSeconds(); // synthesize a date header if none exists |
|
423 |
|
424 // Try HTTP/1.0 style expires header... |
|
425 if (NS_SUCCEEDED(GetExpiresValue(&date2))) { |
|
426 if (date2 > date) |
|
427 *result = date2 - date; |
|
428 // the Expires header can specify a date in the past. |
|
429 return NS_OK; |
|
430 } |
|
431 |
|
432 // Fallback on heuristic using last modified header... |
|
433 if (NS_SUCCEEDED(GetLastModifiedValue(&date2))) { |
|
434 LOG(("using last-modified to determine freshness-lifetime\n")); |
|
435 LOG(("last-modified = %u, date = %u\n", date2, date)); |
|
436 if (date2 <= date) { |
|
437 // this only makes sense if last-modified is actually in the past |
|
438 *result = (date - date2) / 10; |
|
439 return NS_OK; |
|
440 } |
|
441 } |
|
442 |
|
443 // These responses can be cached indefinitely. |
|
444 if ((mStatus == 300) || nsHttp::IsPermanentRedirect(mStatus)) { |
|
445 *result = uint32_t(-1); |
|
446 return NS_OK; |
|
447 } |
|
448 |
|
449 LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %x] " |
|
450 "Insufficient information to compute a non-zero freshness " |
|
451 "lifetime!\n", this)); |
|
452 |
|
453 return NS_OK; |
|
454 } |
|
455 |
|
456 bool |
|
457 nsHttpResponseHead::MustValidate() const |
|
458 { |
|
459 LOG(("nsHttpResponseHead::MustValidate ??\n")); |
|
460 |
|
461 // Some response codes are cacheable, but the rest are not. This switch |
|
462 // should stay in sync with the list in nsHttpChannel::ProcessResponse |
|
463 switch (mStatus) { |
|
464 // Success codes |
|
465 case 200: |
|
466 case 203: |
|
467 case 206: |
|
468 // Cacheable redirects |
|
469 case 300: |
|
470 case 301: |
|
471 case 302: |
|
472 case 304: |
|
473 case 307: |
|
474 case 308: |
|
475 break; |
|
476 // Uncacheable redirects |
|
477 case 303: |
|
478 case 305: |
|
479 // Other known errors |
|
480 case 401: |
|
481 case 407: |
|
482 case 412: |
|
483 case 416: |
|
484 default: // revalidate unknown error pages |
|
485 LOG(("Must validate since response is an uncacheable error page\n")); |
|
486 return true; |
|
487 } |
|
488 |
|
489 // The no-cache response header indicates that we must validate this |
|
490 // cached response before reusing. |
|
491 if (NoCache()) { |
|
492 LOG(("Must validate since response contains 'no-cache' header\n")); |
|
493 return true; |
|
494 } |
|
495 |
|
496 // Likewise, if the response is no-store, then we must validate this |
|
497 // cached response before reusing. NOTE: it may seem odd that a no-store |
|
498 // response may be cached, but indeed all responses are cached in order |
|
499 // to support File->SaveAs, View->PageSource, and other browser features. |
|
500 if (NoStore()) { |
|
501 LOG(("Must validate since response contains 'no-store' header\n")); |
|
502 return true; |
|
503 } |
|
504 |
|
505 // Compare the Expires header to the Date header. If the server sent an |
|
506 // Expires header with a timestamp in the past, then we must validate this |
|
507 // cached response before reusing. |
|
508 if (ExpiresInPast()) { |
|
509 LOG(("Must validate since Expires < Date\n")); |
|
510 return true; |
|
511 } |
|
512 |
|
513 LOG(("no mandatory validation requirement\n")); |
|
514 return false; |
|
515 } |
|
516 |
|
517 bool |
|
518 nsHttpResponseHead::MustValidateIfExpired() const |
|
519 { |
|
520 // according to RFC2616, section 14.9.4: |
|
521 // |
|
522 // When the must-revalidate directive is present in a response received by a |
|
523 // cache, that cache MUST NOT use the entry after it becomes stale to respond to |
|
524 // a subsequent request without first revalidating it with the origin server. |
|
525 // |
|
526 return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate"); |
|
527 } |
|
528 |
|
529 bool |
|
530 nsHttpResponseHead::IsResumable() const |
|
531 { |
|
532 // even though some HTTP/1.0 servers may support byte range requests, we're not |
|
533 // going to bother with them, since those servers wouldn't understand If-Range. |
|
534 // Also, while in theory it may be possible to resume when the status code |
|
535 // is not 200, it is unlikely to be worth the trouble, especially for |
|
536 // non-2xx responses. |
|
537 return mStatus == 200 && |
|
538 mVersion >= NS_HTTP_VERSION_1_1 && |
|
539 PeekHeader(nsHttp::Content_Length) && |
|
540 (PeekHeader(nsHttp::ETag) || PeekHeader(nsHttp::Last_Modified)) && |
|
541 HasHeaderValue(nsHttp::Accept_Ranges, "bytes"); |
|
542 } |
|
543 |
|
544 bool |
|
545 nsHttpResponseHead::ExpiresInPast() const |
|
546 { |
|
547 uint32_t maxAgeVal, expiresVal, dateVal; |
|
548 |
|
549 // Bug #203271. Ensure max-age directive takes precedence over Expires |
|
550 if (NS_SUCCEEDED(GetMaxAgeValue(&maxAgeVal))) { |
|
551 return false; |
|
552 } |
|
553 |
|
554 return NS_SUCCEEDED(GetExpiresValue(&expiresVal)) && |
|
555 NS_SUCCEEDED(GetDateValue(&dateVal)) && |
|
556 expiresVal < dateVal; |
|
557 } |
|
558 |
|
559 nsresult |
|
560 nsHttpResponseHead::UpdateHeaders(const nsHttpHeaderArray &headers) |
|
561 { |
|
562 LOG(("nsHttpResponseHead::UpdateHeaders [this=%p]\n", this)); |
|
563 |
|
564 uint32_t i, count = headers.Count(); |
|
565 for (i=0; i<count; ++i) { |
|
566 nsHttpAtom header; |
|
567 const char *val = headers.PeekHeaderAt(i, header); |
|
568 |
|
569 if (!val) { |
|
570 continue; |
|
571 } |
|
572 |
|
573 // Ignore any hop-by-hop headers... |
|
574 if (header == nsHttp::Connection || |
|
575 header == nsHttp::Proxy_Connection || |
|
576 header == nsHttp::Keep_Alive || |
|
577 header == nsHttp::Proxy_Authenticate || |
|
578 header == nsHttp::Proxy_Authorization || // not a response header! |
|
579 header == nsHttp::TE || |
|
580 header == nsHttp::Trailer || |
|
581 header == nsHttp::Transfer_Encoding || |
|
582 header == nsHttp::Upgrade || |
|
583 // Ignore any non-modifiable headers... |
|
584 header == nsHttp::Content_Location || |
|
585 header == nsHttp::Content_MD5 || |
|
586 header == nsHttp::ETag || |
|
587 // Assume Cache-Control: "no-transform" |
|
588 header == nsHttp::Content_Encoding || |
|
589 header == nsHttp::Content_Range || |
|
590 header == nsHttp::Content_Type || |
|
591 // Ignore wacky headers too... |
|
592 // this one is for MS servers that send "Content-Length: 0" |
|
593 // on 304 responses |
|
594 header == nsHttp::Content_Length) { |
|
595 LOG(("ignoring response header [%s: %s]\n", header.get(), val)); |
|
596 } |
|
597 else { |
|
598 LOG(("new response header [%s: %s]\n", header.get(), val)); |
|
599 |
|
600 // overwrite the current header value with the new value... |
|
601 SetHeader(header, nsDependentCString(val)); |
|
602 } |
|
603 } |
|
604 |
|
605 return NS_OK; |
|
606 } |
|
607 |
|
608 void |
|
609 nsHttpResponseHead::Reset() |
|
610 { |
|
611 LOG(("nsHttpResponseHead::Reset\n")); |
|
612 |
|
613 ClearHeaders(); |
|
614 |
|
615 mVersion = NS_HTTP_VERSION_1_1; |
|
616 mStatus = 200; |
|
617 mContentLength = UINT64_MAX; |
|
618 mCacheControlNoStore = false; |
|
619 mCacheControlNoCache = false; |
|
620 mPragmaNoCache = false; |
|
621 mStatusText.Truncate(); |
|
622 mContentType.Truncate(); |
|
623 mContentCharset.Truncate(); |
|
624 } |
|
625 |
|
626 nsresult |
|
627 nsHttpResponseHead::ParseDateHeader(nsHttpAtom header, uint32_t *result) const |
|
628 { |
|
629 const char *val = PeekHeader(header); |
|
630 if (!val) |
|
631 return NS_ERROR_NOT_AVAILABLE; |
|
632 |
|
633 PRTime time; |
|
634 PRStatus st = PR_ParseTimeString(val, true, &time); |
|
635 if (st != PR_SUCCESS) |
|
636 return NS_ERROR_NOT_AVAILABLE; |
|
637 |
|
638 *result = PRTimeToSeconds(time); |
|
639 return NS_OK; |
|
640 } |
|
641 |
|
642 nsresult |
|
643 nsHttpResponseHead::GetAgeValue(uint32_t *result) const |
|
644 { |
|
645 const char *val = PeekHeader(nsHttp::Age); |
|
646 if (!val) |
|
647 return NS_ERROR_NOT_AVAILABLE; |
|
648 |
|
649 *result = (uint32_t) atoi(val); |
|
650 return NS_OK; |
|
651 } |
|
652 |
|
653 // Return the value of the (HTTP 1.1) max-age directive, which itself is a |
|
654 // component of the Cache-Control response header |
|
655 nsresult |
|
656 nsHttpResponseHead::GetMaxAgeValue(uint32_t *result) const |
|
657 { |
|
658 const char *val = PeekHeader(nsHttp::Cache_Control); |
|
659 if (!val) |
|
660 return NS_ERROR_NOT_AVAILABLE; |
|
661 |
|
662 const char *p = nsHttp::FindToken(val, "max-age", HTTP_HEADER_VALUE_SEPS "="); |
|
663 if (!p) |
|
664 return NS_ERROR_NOT_AVAILABLE; |
|
665 p += 7; |
|
666 while (*p == ' ' || *p == '\t') |
|
667 ++p; |
|
668 if (*p != '=') |
|
669 return NS_ERROR_NOT_AVAILABLE; |
|
670 ++p; |
|
671 while (*p == ' ' || *p == '\t') |
|
672 ++p; |
|
673 |
|
674 int maxAgeValue = atoi(p); |
|
675 if (maxAgeValue < 0) |
|
676 maxAgeValue = 0; |
|
677 *result = static_cast<uint32_t>(maxAgeValue); |
|
678 return NS_OK; |
|
679 } |
|
680 |
|
681 nsresult |
|
682 nsHttpResponseHead::GetExpiresValue(uint32_t *result) const |
|
683 { |
|
684 const char *val = PeekHeader(nsHttp::Expires); |
|
685 if (!val) |
|
686 return NS_ERROR_NOT_AVAILABLE; |
|
687 |
|
688 PRTime time; |
|
689 PRStatus st = PR_ParseTimeString(val, true, &time); |
|
690 if (st != PR_SUCCESS) { |
|
691 // parsing failed... RFC 2616 section 14.21 says we should treat this |
|
692 // as an expiration time in the past. |
|
693 *result = 0; |
|
694 return NS_OK; |
|
695 } |
|
696 |
|
697 if (time < 0) |
|
698 *result = 0; |
|
699 else |
|
700 *result = PRTimeToSeconds(time); |
|
701 return NS_OK; |
|
702 } |
|
703 |
|
704 int64_t |
|
705 nsHttpResponseHead::TotalEntitySize() const |
|
706 { |
|
707 const char* contentRange = PeekHeader(nsHttp::Content_Range); |
|
708 if (!contentRange) |
|
709 return ContentLength(); |
|
710 |
|
711 // Total length is after a slash |
|
712 const char* slash = strrchr(contentRange, '/'); |
|
713 if (!slash) |
|
714 return -1; // No idea what the length is |
|
715 |
|
716 slash++; |
|
717 if (*slash == '*') // Server doesn't know the length |
|
718 return -1; |
|
719 |
|
720 int64_t size; |
|
721 if (!nsHttp::ParseInt64(slash, &size)) |
|
722 size = UINT64_MAX; |
|
723 return size; |
|
724 } |
|
725 |
|
726 //----------------------------------------------------------------------------- |
|
727 // nsHttpResponseHead <private> |
|
728 //----------------------------------------------------------------------------- |
|
729 |
|
730 void |
|
731 nsHttpResponseHead::ParseVersion(const char *str) |
|
732 { |
|
733 // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT |
|
734 |
|
735 LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str)); |
|
736 |
|
737 // make sure we have HTTP at the beginning |
|
738 if (PL_strncasecmp(str, "HTTP", 4) != 0) { |
|
739 if (PL_strncasecmp(str, "ICY ", 4) == 0) { |
|
740 // ShoutCast ICY is HTTP/1.0-like. Assume it is HTTP/1.0. |
|
741 LOG(("Treating ICY as HTTP 1.0\n")); |
|
742 mVersion = NS_HTTP_VERSION_1_0; |
|
743 return; |
|
744 } |
|
745 LOG(("looks like a HTTP/0.9 response\n")); |
|
746 mVersion = NS_HTTP_VERSION_0_9; |
|
747 return; |
|
748 } |
|
749 str += 4; |
|
750 |
|
751 if (*str != '/') { |
|
752 LOG(("server did not send a version number; assuming HTTP/1.0\n")); |
|
753 // NCSA/1.5.2 has a bug in which it fails to send a version number |
|
754 // if the request version is HTTP/1.1, so we fall back on HTTP/1.0 |
|
755 mVersion = NS_HTTP_VERSION_1_0; |
|
756 return; |
|
757 } |
|
758 |
|
759 char *p = PL_strchr(str, '.'); |
|
760 if (p == nullptr) { |
|
761 LOG(("mal-formed server version; assuming HTTP/1.0\n")); |
|
762 mVersion = NS_HTTP_VERSION_1_0; |
|
763 return; |
|
764 } |
|
765 |
|
766 ++p; // let b point to the minor version |
|
767 |
|
768 int major = atoi(str + 1); |
|
769 int minor = atoi(p); |
|
770 |
|
771 if ((major > 2) || ((major == 2) && (minor >= 0))) |
|
772 mVersion = NS_HTTP_VERSION_2_0; |
|
773 else if ((major == 1) && (minor >= 1)) |
|
774 // at least HTTP/1.1 |
|
775 mVersion = NS_HTTP_VERSION_1_1; |
|
776 else |
|
777 // treat anything else as version 1.0 |
|
778 mVersion = NS_HTTP_VERSION_1_0; |
|
779 } |
|
780 |
|
781 void |
|
782 nsHttpResponseHead::ParseCacheControl(const char *val) |
|
783 { |
|
784 if (!(val && *val)) { |
|
785 // clear flags |
|
786 mCacheControlNoCache = false; |
|
787 mCacheControlNoStore = false; |
|
788 return; |
|
789 } |
|
790 |
|
791 // search header value for occurrence(s) of "no-cache" but ignore |
|
792 // occurrence(s) of "no-cache=blah" |
|
793 if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS)) |
|
794 mCacheControlNoCache = true; |
|
795 |
|
796 // search header value for occurrence of "no-store" |
|
797 if (nsHttp::FindToken(val, "no-store", HTTP_HEADER_VALUE_SEPS)) |
|
798 mCacheControlNoStore = true; |
|
799 } |
|
800 |
|
801 void |
|
802 nsHttpResponseHead::ParsePragma(const char *val) |
|
803 { |
|
804 LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val)); |
|
805 |
|
806 if (!(val && *val)) { |
|
807 // clear no-cache flag |
|
808 mPragmaNoCache = false; |
|
809 return; |
|
810 } |
|
811 |
|
812 // Although 'Pragma: no-cache' is not a standard HTTP response header (it's |
|
813 // a request header), caching is inhibited when this header is present so |
|
814 // as to match existing Navigator behavior. |
|
815 if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS)) |
|
816 mPragmaNoCache = true; |
|
817 } |
|
818 |
|
819 } // namespace mozilla::net |
|
820 } // namespace mozilla |