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