mirror of
https://github.com/roytam1/UXP.git
synced 2026-05-26 13:58:49 +00:00
30797d4da8
Potential attack: session supercookie. [Moz Notes](https://bugzilla.mozilla.org/show_bug.cgi?id=1334776#c5): "The problem is that for unknown header names we store the first one we see and then later we case-insensitively match against that name *globally*. That means you can track if a user agent has already seen a certain header name used (by using a different casing and observing whether it gets normalized). This would allow you to see if a user has used a sensitive service that uses custom header names, or allows you to track a user across sites, by teaching the browser about a certain header case once and then observing if different casings get normalized to that. What we should do instead is only store the casing for a header name for each header list and not globally. That way it only leaks where it's expected (and necessary) to leak." [Moz fix note](https://bugzilla.mozilla.org/show_bug.cgi?id=1334776#c8): "nsHttpAtom now holds the old nsHttpAtom and a string that is case sensitive (only for not standard headers). So nsHttpAtom holds a pointer to a header name. (header names are store on a static structure). This is how it used to be. I left that part the same but added a nsCString which holds a string that was used to resoled the header name. So when we parse headers we call ResolveHeader with a char*. If it is a new header name the char* will be stored in a HttpHeapAtom, nsHttpAtom::_val will point to HttpHeapAtom::value and the same strings will be stored in mLocalCaseSensitiveHeader. For the first resolve request they will be the same but for the following maybe not. At the end this nsHttpAtom will be stored in nsHttpHeaderArray. For all operation we will used the old char* except when we are returning it to a script using VisitHeaders."
1250 lines
36 KiB
C++
1250 lines
36 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/* vim:set ts=4 sw=4 sts=4 et cin: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// HttpLog.h should generally be included first
|
|
#include "HttpLog.h"
|
|
|
|
#include "nsHttpResponseHead.h"
|
|
#include "nsIHttpHeaderVisitor.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "prtime.h"
|
|
#include "plstr.h"
|
|
#include "nsURLHelper.h"
|
|
#include <algorithm>
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsHttpResponseHead <public>
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsHttpResponseHead::nsHttpResponseHead(const nsHttpResponseHead &aOther)
|
|
: mReentrantMonitor("nsHttpResponseHead.mReentrantMonitor")
|
|
, mInVisitHeaders(false)
|
|
{
|
|
nsHttpResponseHead &other = const_cast<nsHttpResponseHead&>(aOther);
|
|
ReentrantMonitorAutoEnter monitor(other.mReentrantMonitor);
|
|
|
|
mHeaders = other.mHeaders;
|
|
mVersion = other.mVersion;
|
|
mStatus = other.mStatus;
|
|
mStatusText = other.mStatusText;
|
|
mContentLength = other.mContentLength;
|
|
mContentType = other.mContentType;
|
|
mContentCharset = other.mContentCharset;
|
|
mCacheControlPrivate = other.mCacheControlPrivate;
|
|
mCacheControlNoStore = other.mCacheControlNoStore;
|
|
mCacheControlNoCache = other.mCacheControlNoCache;
|
|
mCacheControlImmutable = other.mCacheControlImmutable;
|
|
mPragmaNoCache = other.mPragmaNoCache;
|
|
}
|
|
|
|
nsHttpResponseHead&
|
|
nsHttpResponseHead::operator=(const nsHttpResponseHead &aOther)
|
|
{
|
|
nsHttpResponseHead &other = const_cast<nsHttpResponseHead&>(aOther);
|
|
ReentrantMonitorAutoEnter monitorOther(other.mReentrantMonitor);
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
|
|
mHeaders = other.mHeaders;
|
|
mVersion = other.mVersion;
|
|
mStatus = other.mStatus;
|
|
mStatusText = other.mStatusText;
|
|
mContentLength = other.mContentLength;
|
|
mContentType = other.mContentType;
|
|
mContentCharset = other.mContentCharset;
|
|
mCacheControlPrivate = other.mCacheControlPrivate;
|
|
mCacheControlNoStore = other.mCacheControlNoStore;
|
|
mCacheControlNoCache = other.mCacheControlNoCache;
|
|
mCacheControlImmutable = other.mCacheControlImmutable;
|
|
mPragmaNoCache = other.mPragmaNoCache;
|
|
|
|
return *this;
|
|
}
|
|
|
|
nsHttpVersion
|
|
nsHttpResponseHead::Version()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mVersion;
|
|
}
|
|
|
|
uint16_t
|
|
nsHttpResponseHead::Status()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mStatus;
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::StatusText(nsACString &aStatusText)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
aStatusText = mStatusText;
|
|
}
|
|
|
|
int64_t
|
|
nsHttpResponseHead::ContentLength()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mContentLength;
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::ContentType(nsACString &aContentType)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
aContentType = mContentType;
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::ContentCharset(nsACString &aContentCharset)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
aContentCharset = mContentCharset;
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::Private()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mCacheControlPrivate;
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::NoStore()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mCacheControlNoStore;
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::NoCache()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return (mCacheControlNoCache || mPragmaNoCache);
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::Immutable()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mCacheControlImmutable;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::SetHeader(const nsACString &hdr,
|
|
const nsACString &val,
|
|
bool merge)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
|
|
if (mInVisitHeaders) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(PromiseFlatCString(hdr).get());
|
|
if (!atom) {
|
|
NS_WARNING("failed to resolve atom");
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return SetHeader_locked(atom, hdr, val, merge);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::SetHeader(nsHttpAtom hdr,
|
|
const nsACString &val,
|
|
bool merge)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
|
|
if (mInVisitHeaders) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return SetHeader_locked(hdr, EmptyCString(), val, merge);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::SetHeader_locked(nsHttpAtom atom,
|
|
const nsACString &hdr,
|
|
const nsACString &val,
|
|
bool merge)
|
|
{
|
|
nsresult rv = mHeaders.SetHeader(atom, hdr, val, merge,
|
|
nsHttpHeaderArray::eVarietyResponse);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// respond to changes in these headers. we need to reparse the entire
|
|
// header since the change may have merged in additional values.
|
|
if (atom == nsHttp::Cache_Control)
|
|
ParseCacheControl(mHeaders.PeekHeader(atom));
|
|
else if (atom == nsHttp::Pragma)
|
|
ParsePragma(mHeaders.PeekHeader(atom));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetHeader(nsHttpAtom h, nsACString &v)
|
|
{
|
|
v.Truncate();
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mHeaders.GetHeader(h, v);
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::ClearHeader(nsHttpAtom h)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
mHeaders.ClearHeader(h);
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::ClearHeaders()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
mHeaders.Clear();
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::HasHeaderValue(nsHttpAtom h, const char *v)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mHeaders.HasHeaderValue(h, v);
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::HasHeader(nsHttpAtom h)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return mHeaders.HasHeader(h);
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::SetContentType(const nsACString &s)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
mContentType = s;
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::SetContentCharset(const nsACString &s)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
mContentCharset = s;
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::SetContentLength(int64_t len)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
|
|
mContentLength = len;
|
|
if (len < 0)
|
|
mHeaders.ClearHeader(nsHttp::Content_Length);
|
|
else
|
|
mHeaders.SetHeader(nsHttp::Content_Length,
|
|
nsPrintfCString("%lld", len),
|
|
false,
|
|
nsHttpHeaderArray::eVarietyResponse);
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::Flatten(nsACString &buf, bool pruneTransients)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
if (mVersion == NS_HTTP_VERSION_0_9)
|
|
return;
|
|
|
|
buf.AppendLiteral("HTTP/");
|
|
if (mVersion == NS_HTTP_VERSION_2_0)
|
|
buf.AppendLiteral("2.0 ");
|
|
else if (mVersion == NS_HTTP_VERSION_1_1)
|
|
buf.AppendLiteral("1.1 ");
|
|
else
|
|
buf.AppendLiteral("1.0 ");
|
|
|
|
buf.Append(nsPrintfCString("%u", unsigned(mStatus)) +
|
|
NS_LITERAL_CSTRING(" ") +
|
|
mStatusText +
|
|
NS_LITERAL_CSTRING("\r\n"));
|
|
|
|
|
|
mHeaders.Flatten(buf, false, pruneTransients);
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::FlattenNetworkOriginalHeaders(nsACString &buf)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
if (mVersion == NS_HTTP_VERSION_0_9) {
|
|
return;
|
|
}
|
|
|
|
mHeaders.FlattenOriginalHeader(buf);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::ParseCachedHead(const char *block)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
LOG(("nsHttpResponseHead::ParseCachedHead [this=%p]\n", this));
|
|
|
|
// this command works on a buffer as prepared by Flatten, as such it is
|
|
// not very forgiving ;-)
|
|
|
|
char *p = PL_strstr(block, "\r\n");
|
|
if (!p)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
ParseStatusLine_locked(nsDependentCSubstring(block, p - block));
|
|
|
|
do {
|
|
block = p + 2;
|
|
|
|
if (*block == 0)
|
|
break;
|
|
|
|
p = PL_strstr(block, "\r\n");
|
|
if (!p)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
ParseHeaderLine_locked(nsDependentCSubstring(block, p - block), false);
|
|
|
|
} while (1);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::ParseCachedOriginalHeaders(char *block)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
LOG(("nsHttpResponseHead::ParseCachedOriginalHeader [this=%p]\n", this));
|
|
|
|
// this command works on a buffer as prepared by FlattenOriginalHeader,
|
|
// as such it is not very forgiving ;-)
|
|
|
|
if (!block) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
char *p = block;
|
|
nsHttpAtom hdr = {0};
|
|
nsAutoCString headerNameOriginal;
|
|
nsAutoCString val;
|
|
nsresult rv;
|
|
|
|
do {
|
|
block = p;
|
|
|
|
if (*block == 0)
|
|
break;
|
|
|
|
p = PL_strstr(block, "\r\n");
|
|
if (!p)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
*p = 0;
|
|
if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(
|
|
nsDependentCString(block, p - block), &hdr, &headerNameOriginal, &val))) {
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
rv = mHeaders.SetResponseHeaderFromCache(hdr,
|
|
headerNameOriginal,
|
|
val,
|
|
nsHttpHeaderArray::eVarietyResponseNetOriginal);
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
p = p + 2;
|
|
} while (1);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::AssignDefaultStatusText()
|
|
{
|
|
LOG(("response status line needs default reason phrase\n"));
|
|
|
|
// if a http response doesn't contain a reason phrase, put one in based
|
|
// on the status code. The reason phrase is totally meaningless so its
|
|
// ok to have a default catch all here - but this makes debuggers and addons
|
|
// a little saner to use if we don't map things to "404 OK" or other nonsense.
|
|
// In particular, HTTP/2 does not use reason phrases at all so they need to
|
|
// always be injected.
|
|
|
|
switch (mStatus) {
|
|
// start with the most common
|
|
case 200:
|
|
mStatusText.AssignLiteral("OK");
|
|
break;
|
|
case 404:
|
|
mStatusText.AssignLiteral("Not Found");
|
|
break;
|
|
case 301:
|
|
mStatusText.AssignLiteral("Moved Permanently");
|
|
break;
|
|
case 304:
|
|
mStatusText.AssignLiteral("Not Modified");
|
|
break;
|
|
case 307:
|
|
mStatusText.AssignLiteral("Temporary Redirect");
|
|
break;
|
|
case 500:
|
|
mStatusText.AssignLiteral("Internal Server Error");
|
|
break;
|
|
|
|
// also well known
|
|
case 100:
|
|
mStatusText.AssignLiteral("Continue");
|
|
break;
|
|
case 101:
|
|
mStatusText.AssignLiteral("Switching Protocols");
|
|
break;
|
|
case 201:
|
|
mStatusText.AssignLiteral("Created");
|
|
break;
|
|
case 202:
|
|
mStatusText.AssignLiteral("Accepted");
|
|
break;
|
|
case 203:
|
|
mStatusText.AssignLiteral("Non Authoritative");
|
|
break;
|
|
case 204:
|
|
mStatusText.AssignLiteral("No Content");
|
|
break;
|
|
case 205:
|
|
mStatusText.AssignLiteral("Reset Content");
|
|
break;
|
|
case 206:
|
|
mStatusText.AssignLiteral("Partial Content");
|
|
break;
|
|
case 207:
|
|
mStatusText.AssignLiteral("Multi-Status");
|
|
break;
|
|
case 208:
|
|
mStatusText.AssignLiteral("Already Reported");
|
|
break;
|
|
case 300:
|
|
mStatusText.AssignLiteral("Multiple Choices");
|
|
break;
|
|
case 302:
|
|
mStatusText.AssignLiteral("Found");
|
|
break;
|
|
case 303:
|
|
mStatusText.AssignLiteral("See Other");
|
|
break;
|
|
case 305:
|
|
mStatusText.AssignLiteral("Use Proxy");
|
|
break;
|
|
case 308:
|
|
mStatusText.AssignLiteral("Permanent Redirect");
|
|
break;
|
|
case 400:
|
|
mStatusText.AssignLiteral("Bad Request");
|
|
break;
|
|
case 401:
|
|
mStatusText.AssignLiteral("Unauthorized");
|
|
break;
|
|
case 402:
|
|
mStatusText.AssignLiteral("Payment Required");
|
|
break;
|
|
case 403:
|
|
mStatusText.AssignLiteral("Forbidden");
|
|
break;
|
|
case 405:
|
|
mStatusText.AssignLiteral("Method Not Allowed");
|
|
break;
|
|
case 406:
|
|
mStatusText.AssignLiteral("Not Acceptable");
|
|
break;
|
|
case 407:
|
|
mStatusText.AssignLiteral("Proxy Authentication Required");
|
|
break;
|
|
case 408:
|
|
mStatusText.AssignLiteral("Request Timeout");
|
|
break;
|
|
case 409:
|
|
mStatusText.AssignLiteral("Conflict");
|
|
break;
|
|
case 410:
|
|
mStatusText.AssignLiteral("Gone");
|
|
break;
|
|
case 411:
|
|
mStatusText.AssignLiteral("Length Required");
|
|
break;
|
|
case 412:
|
|
mStatusText.AssignLiteral("Precondition Failed");
|
|
break;
|
|
case 413:
|
|
mStatusText.AssignLiteral("Request Entity Too Large");
|
|
break;
|
|
case 414:
|
|
mStatusText.AssignLiteral("Request URI Too Long");
|
|
break;
|
|
case 415:
|
|
mStatusText.AssignLiteral("Unsupported Media Type");
|
|
break;
|
|
case 416:
|
|
mStatusText.AssignLiteral("Requested Range Not Satisfiable");
|
|
break;
|
|
case 417:
|
|
mStatusText.AssignLiteral("Expectation Failed");
|
|
break;
|
|
case 421:
|
|
mStatusText.AssignLiteral("Misdirected Request");
|
|
break;
|
|
case 501:
|
|
mStatusText.AssignLiteral("Not Implemented");
|
|
break;
|
|
case 502:
|
|
mStatusText.AssignLiteral("Bad Gateway");
|
|
break;
|
|
case 503:
|
|
mStatusText.AssignLiteral("Service Unavailable");
|
|
break;
|
|
case 504:
|
|
mStatusText.AssignLiteral("Gateway Timeout");
|
|
break;
|
|
case 505:
|
|
mStatusText.AssignLiteral("HTTP Version Unsupported");
|
|
break;
|
|
default:
|
|
mStatusText.AssignLiteral("No Reason Phrase");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::ParseStatusLine(const nsACString &line)
|
|
{
|
|
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
ParseStatusLine_locked(line);
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::ParseStatusLine_locked(const nsACString &line)
|
|
{
|
|
//
|
|
// Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
|
|
//
|
|
|
|
const char *start = line.BeginReading();
|
|
const char *end = line.EndReading();
|
|
const char *p = start;
|
|
|
|
// HTTP-Version
|
|
ParseVersion(start);
|
|
|
|
int32_t index = line.FindChar(' ');
|
|
|
|
if ((mVersion == NS_HTTP_VERSION_0_9) || (index == -1)) {
|
|
mStatus = 200;
|
|
AssignDefaultStatusText();
|
|
}
|
|
else {
|
|
// Status-Code
|
|
p += index + 1;
|
|
mStatus = (uint16_t) atoi(p);
|
|
if (mStatus == 0) {
|
|
LOG(("mal-formed response status; assuming status = 200\n"));
|
|
mStatus = 200;
|
|
}
|
|
|
|
// Reason-Phrase is whatever is remaining of the line
|
|
index = line.FindChar(' ', p - start);
|
|
if (index == -1) {
|
|
AssignDefaultStatusText();
|
|
}
|
|
else {
|
|
p = start + index + 1;
|
|
mStatusText = nsDependentCSubstring(p, end - p);
|
|
}
|
|
}
|
|
|
|
LOG(("Have status line [version=%u status=%u statusText=%s]\n",
|
|
unsigned(mVersion), unsigned(mStatus), mStatusText.get()));
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::ParseHeaderLine(const nsACString &line)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return ParseHeaderLine_locked(line, true);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::ParseHeaderLine_locked(const nsACString &line, bool originalFromNetHeaders)
|
|
{
|
|
nsHttpAtom hdr = {0};
|
|
nsAutoCString headerNameOriginal;
|
|
nsAutoCString val;
|
|
|
|
if (NS_FAILED(nsHttpHeaderArray::ParseHeaderLine(line, &hdr, &headerNameOriginal, &val))) {
|
|
return NS_OK;
|
|
}
|
|
nsresult rv;
|
|
if (originalFromNetHeaders) {
|
|
rv = mHeaders.SetHeaderFromNet(hdr,
|
|
headerNameOriginal,
|
|
val,
|
|
true);
|
|
} else {
|
|
rv = mHeaders.SetResponseHeaderFromCache(hdr,
|
|
headerNameOriginal,
|
|
val,
|
|
nsHttpHeaderArray::eVarietyResponse);
|
|
}
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
// leading and trailing LWS has been removed from |val|
|
|
|
|
// handle some special case headers...
|
|
if (hdr == nsHttp::Content_Length) {
|
|
int64_t len;
|
|
const char *ignored;
|
|
// permit only a single value here.
|
|
if (nsHttp::ParseInt64(val.get(), &ignored, &len)) {
|
|
mContentLength = len;
|
|
}
|
|
else {
|
|
// If this is a negative content length then just ignore it
|
|
LOG(("invalid content-length! %s\n", val.get()));
|
|
}
|
|
}
|
|
else if (hdr == nsHttp::Content_Type) {
|
|
LOG(("ParseContentType [type=%s]\n", val.get()));
|
|
bool dummy;
|
|
net_ParseContentType(val,
|
|
mContentType, mContentCharset, &dummy);
|
|
}
|
|
else if (hdr == nsHttp::Cache_Control)
|
|
ParseCacheControl(val.get());
|
|
else if (hdr == nsHttp::Pragma)
|
|
ParsePragma(val.get());
|
|
return NS_OK;
|
|
}
|
|
|
|
// From section 13.2.3 of RFC2616, we compute the current age of a cached
|
|
// response as follows:
|
|
//
|
|
// currentAge = max(max(0, responseTime - dateValue), ageValue)
|
|
// + now - requestTime
|
|
//
|
|
// where responseTime == now
|
|
//
|
|
// This is typically a very small number.
|
|
//
|
|
nsresult
|
|
nsHttpResponseHead::ComputeCurrentAge(uint32_t now,
|
|
uint32_t requestTime,
|
|
uint32_t *result)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
uint32_t dateValue;
|
|
uint32_t ageValue;
|
|
|
|
*result = 0;
|
|
|
|
if (requestTime > now) {
|
|
// for calculation purposes lets not allow the request to happen in the future
|
|
requestTime = now;
|
|
}
|
|
|
|
if (NS_FAILED(GetDateValue_locked(&dateValue))) {
|
|
LOG(("nsHttpResponseHead::ComputeCurrentAge [this=%p] "
|
|
"Date response header not set!\n", this));
|
|
// Assume we have a fast connection and that our clock
|
|
// is in sync with the server.
|
|
dateValue = now;
|
|
}
|
|
|
|
// Compute apparent age
|
|
if (now > dateValue)
|
|
*result = now - dateValue;
|
|
|
|
// Compute corrected received age
|
|
if (NS_SUCCEEDED(GetAgeValue_locked(&ageValue)))
|
|
*result = std::max(*result, ageValue);
|
|
|
|
// Compute current age
|
|
*result += (now - requestTime);
|
|
return NS_OK;
|
|
}
|
|
|
|
// From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
|
|
// response as follows:
|
|
//
|
|
// freshnessLifetime = max_age_value
|
|
// <or>
|
|
// freshnessLifetime = expires_value - date_value
|
|
// <or>
|
|
// freshnessLifetime = min(one-week,(date_value - last_modified_value) * 0.10)
|
|
// <or>
|
|
// freshnessLifetime = 0
|
|
//
|
|
nsresult
|
|
nsHttpResponseHead::ComputeFreshnessLifetime(uint32_t *result)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
*result = 0;
|
|
|
|
// Try HTTP/1.1 style max-age directive...
|
|
if (NS_SUCCEEDED(GetMaxAgeValue_locked(result)))
|
|
return NS_OK;
|
|
|
|
*result = 0;
|
|
|
|
uint32_t date = 0, date2 = 0;
|
|
if (NS_FAILED(GetDateValue_locked(&date)))
|
|
date = NowInSeconds(); // synthesize a date header if none exists
|
|
|
|
// Try HTTP/1.0 style expires header...
|
|
if (NS_SUCCEEDED(GetExpiresValue_locked(&date2))) {
|
|
if (date2 > date)
|
|
*result = date2 - date;
|
|
// the Expires header can specify a date in the past.
|
|
return NS_OK;
|
|
}
|
|
|
|
// These responses can be cached indefinitely.
|
|
if ((mStatus == 300) || (mStatus == 410) || nsHttp::IsPermanentRedirect(mStatus)) {
|
|
LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
|
|
"Assign an infinite heuristic lifetime\n", this));
|
|
*result = uint32_t(-1);
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mStatus >= 400) {
|
|
LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
|
|
"Do not calculate heuristic max-age for most responses >= 400\n", this));
|
|
return NS_OK;
|
|
}
|
|
|
|
// Fallback on heuristic using last modified header...
|
|
if (NS_SUCCEEDED(GetLastModifiedValue_locked(&date2))) {
|
|
LOG(("using last-modified to determine freshness-lifetime\n"));
|
|
LOG(("last-modified = %u, date = %u\n", date2, date));
|
|
if (date2 <= date) {
|
|
// this only makes sense if last-modified is actually in the past
|
|
*result = (date - date2) / 10;
|
|
const uint32_t kOneWeek = 60 * 60 * 24 * 7;
|
|
*result = std::min(kOneWeek, *result);
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %p] "
|
|
"Insufficient information to compute a non-zero freshness "
|
|
"lifetime!\n", this));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::MustValidate()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
LOG(("nsHttpResponseHead::MustValidate ??\n"));
|
|
|
|
// Some response codes are cacheable, but the rest are not. This switch
|
|
// should stay in sync with the list in nsHttpChannel::ProcessResponse
|
|
switch (mStatus) {
|
|
// Success codes
|
|
case 200:
|
|
case 203:
|
|
case 206:
|
|
// Cacheable redirects
|
|
case 300:
|
|
case 301:
|
|
case 302:
|
|
case 304:
|
|
case 307:
|
|
case 308:
|
|
// Gone forever
|
|
case 410:
|
|
break;
|
|
// Uncacheable redirects
|
|
case 303:
|
|
case 305:
|
|
// Other known errors
|
|
case 401:
|
|
case 407:
|
|
case 412:
|
|
case 416:
|
|
default: // revalidate unknown error pages
|
|
LOG(("Must validate since response is an uncacheable error page\n"));
|
|
return true;
|
|
}
|
|
|
|
// The no-cache response header indicates that we must validate this
|
|
// cached response before reusing.
|
|
if (mCacheControlNoCache || mPragmaNoCache) {
|
|
LOG(("Must validate since response contains 'no-cache' header\n"));
|
|
return true;
|
|
}
|
|
|
|
// Likewise, if the response is no-store, then we must validate this
|
|
// cached response before reusing. NOTE: it may seem odd that a no-store
|
|
// response may be cached, but indeed all responses are cached in order
|
|
// to support File->SaveAs, View->PageSource, and other browser features.
|
|
if (mCacheControlNoStore) {
|
|
LOG(("Must validate since response contains 'no-store' header\n"));
|
|
return true;
|
|
}
|
|
|
|
// Compare the Expires header to the Date header. If the server sent an
|
|
// Expires header with a timestamp in the past, then we must validate this
|
|
// cached response before reusing.
|
|
if (ExpiresInPast_locked()) {
|
|
LOG(("Must validate since Expires < Date\n"));
|
|
return true;
|
|
}
|
|
|
|
LOG(("no mandatory validation requirement\n"));
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::MustValidateIfExpired()
|
|
{
|
|
// according to RFC2616, section 14.9.4:
|
|
//
|
|
// When the must-revalidate directive is present in a response received by a
|
|
// cache, that cache MUST NOT use the entry after it becomes stale to respond to
|
|
// a subsequent request without first revalidating it with the origin server.
|
|
//
|
|
return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate");
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::IsResumable()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
// even though some HTTP/1.0 servers may support byte range requests, we're not
|
|
// going to bother with them, since those servers wouldn't understand If-Range.
|
|
// Also, while in theory it may be possible to resume when the status code
|
|
// is not 200, it is unlikely to be worth the trouble, especially for
|
|
// non-2xx responses.
|
|
return mStatus == 200 &&
|
|
mVersion >= NS_HTTP_VERSION_1_1 &&
|
|
mHeaders.PeekHeader(nsHttp::Content_Length) &&
|
|
(mHeaders.PeekHeader(nsHttp::ETag) ||
|
|
mHeaders.PeekHeader(nsHttp::Last_Modified)) &&
|
|
mHeaders.HasHeaderValue(nsHttp::Accept_Ranges, "bytes");
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::ExpiresInPast()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return ExpiresInPast_locked();
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::ExpiresInPast_locked() const
|
|
{
|
|
uint32_t maxAgeVal, expiresVal, dateVal;
|
|
|
|
// Bug #203271. Ensure max-age directive takes precedence over Expires
|
|
if (NS_SUCCEEDED(GetMaxAgeValue_locked(&maxAgeVal))) {
|
|
return false;
|
|
}
|
|
|
|
return NS_SUCCEEDED(GetExpiresValue_locked(&expiresVal)) &&
|
|
NS_SUCCEEDED(GetDateValue_locked(&dateVal)) &&
|
|
expiresVal < dateVal;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::UpdateHeaders(nsHttpResponseHead *aOther)
|
|
{
|
|
LOG(("nsHttpResponseHead::UpdateHeaders [this=%p]\n", this));
|
|
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
ReentrantMonitorAutoEnter monitorOther(aOther->mReentrantMonitor);
|
|
|
|
uint32_t i, count = aOther->mHeaders.Count();
|
|
for (i=0; i<count; ++i) {
|
|
nsHttpAtom header;
|
|
nsAutoCString headerNameOriginal;
|
|
const char *val = aOther->mHeaders.PeekHeaderAt(i, header, headerNameOriginal);
|
|
|
|
if (!val) {
|
|
continue;
|
|
}
|
|
|
|
// Ignore any hop-by-hop headers...
|
|
if (header == nsHttp::Connection ||
|
|
header == nsHttp::Proxy_Connection ||
|
|
header == nsHttp::Keep_Alive ||
|
|
header == nsHttp::Proxy_Authenticate ||
|
|
header == nsHttp::Proxy_Authorization || // not a response header!
|
|
header == nsHttp::TE ||
|
|
header == nsHttp::Trailer ||
|
|
header == nsHttp::Transfer_Encoding ||
|
|
header == nsHttp::Upgrade ||
|
|
// Ignore any non-modifiable headers...
|
|
header == nsHttp::Content_Location ||
|
|
header == nsHttp::Content_MD5 ||
|
|
header == nsHttp::ETag ||
|
|
// Assume Cache-Control: "no-transform"
|
|
header == nsHttp::Content_Encoding ||
|
|
header == nsHttp::Content_Range ||
|
|
header == nsHttp::Content_Type ||
|
|
// Ignore wacky headers too...
|
|
// this one is for MS servers that send "Content-Length: 0"
|
|
// on 304 responses
|
|
header == nsHttp::Content_Length) {
|
|
LOG(("ignoring response header [%s: %s]\n", header.get(), val));
|
|
}
|
|
else {
|
|
LOG(("new response header [%s: %s]\n", header.get(), val));
|
|
|
|
// overwrite the current header value with the new value...
|
|
SetHeader_locked(header, headerNameOriginal,
|
|
nsDependentCString(val));
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::Reset()
|
|
{
|
|
LOG(("nsHttpResponseHead::Reset\n"));
|
|
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
|
|
mHeaders.Clear();
|
|
|
|
mVersion = NS_HTTP_VERSION_1_1;
|
|
mStatus = 200;
|
|
mContentLength = -1;
|
|
mCacheControlPrivate = false;
|
|
mCacheControlNoStore = false;
|
|
mCacheControlNoCache = false;
|
|
mCacheControlImmutable = false;
|
|
mPragmaNoCache = false;
|
|
mStatusText.Truncate();
|
|
mContentType.Truncate();
|
|
mContentCharset.Truncate();
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::ParseDateHeader(nsHttpAtom header, uint32_t *result) const
|
|
{
|
|
const char *val = mHeaders.PeekHeader(header);
|
|
if (!val)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
PRTime time;
|
|
PRStatus st = PR_ParseTimeString(val, true, &time);
|
|
if (st != PR_SUCCESS)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = PRTimeToSeconds(time);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetAgeValue(uint32_t *result)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return GetAgeValue_locked(result);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetAgeValue_locked(uint32_t *result) const
|
|
{
|
|
const char *val = mHeaders.PeekHeader(nsHttp::Age);
|
|
if (!val)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
*result = (uint32_t) atoi(val);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Return the value of the (HTTP 1.1) max-age directive, which itself is a
|
|
// component of the Cache-Control response header
|
|
nsresult
|
|
nsHttpResponseHead::GetMaxAgeValue(uint32_t *result)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return GetMaxAgeValue_locked(result);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetMaxAgeValue_locked(uint32_t *result) const
|
|
{
|
|
const char *val = mHeaders.PeekHeader(nsHttp::Cache_Control);
|
|
if (!val)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
const char *p = nsHttp::FindToken(val, "max-age", HTTP_HEADER_VALUE_SEPS "=");
|
|
if (!p)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
p += 7;
|
|
while (*p == ' ' || *p == '\t')
|
|
++p;
|
|
if (*p != '=')
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
++p;
|
|
while (*p == ' ' || *p == '\t')
|
|
++p;
|
|
|
|
int maxAgeValue = atoi(p);
|
|
if (maxAgeValue < 0)
|
|
maxAgeValue = 0;
|
|
*result = static_cast<uint32_t>(maxAgeValue);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetDateValue(uint32_t *result)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return GetDateValue_locked(result);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetExpiresValue(uint32_t *result)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return GetExpiresValue_locked(result);
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetExpiresValue_locked(uint32_t *result) const
|
|
{
|
|
const char *val = mHeaders.PeekHeader(nsHttp::Expires);
|
|
if (!val)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
PRTime time;
|
|
PRStatus st = PR_ParseTimeString(val, true, &time);
|
|
if (st != PR_SUCCESS) {
|
|
// parsing failed... RFC 2616 section 14.21 says we should treat this
|
|
// as an expiration time in the past.
|
|
*result = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (time < 0)
|
|
*result = 0;
|
|
else
|
|
*result = PRTimeToSeconds(time);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetLastModifiedValue(uint32_t *result)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return ParseDateHeader(nsHttp::Last_Modified, result);
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::operator==(const nsHttpResponseHead& aOther) const
|
|
{
|
|
nsHttpResponseHead &curr = const_cast<nsHttpResponseHead&>(*this);
|
|
nsHttpResponseHead &other = const_cast<nsHttpResponseHead&>(aOther);
|
|
ReentrantMonitorAutoEnter monitorOther(other.mReentrantMonitor);
|
|
ReentrantMonitorAutoEnter monitor(curr.mReentrantMonitor);
|
|
|
|
return mHeaders == aOther.mHeaders &&
|
|
mVersion == aOther.mVersion &&
|
|
mStatus == aOther.mStatus &&
|
|
mStatusText == aOther.mStatusText &&
|
|
mContentLength == aOther.mContentLength &&
|
|
mContentType == aOther.mContentType &&
|
|
mContentCharset == aOther.mContentCharset &&
|
|
mCacheControlPrivate == aOther.mCacheControlPrivate &&
|
|
mCacheControlNoCache == aOther.mCacheControlNoCache &&
|
|
mCacheControlNoStore == aOther.mCacheControlNoStore &&
|
|
mCacheControlImmutable == aOther.mCacheControlImmutable &&
|
|
mPragmaNoCache == aOther.mPragmaNoCache;
|
|
}
|
|
|
|
int64_t
|
|
nsHttpResponseHead::TotalEntitySize()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
const char* contentRange = mHeaders.PeekHeader(nsHttp::Content_Range);
|
|
if (!contentRange)
|
|
return mContentLength;
|
|
|
|
// Total length is after a slash
|
|
const char* slash = strrchr(contentRange, '/');
|
|
if (!slash)
|
|
return -1; // No idea what the length is
|
|
|
|
slash++;
|
|
if (*slash == '*') // Server doesn't know the length
|
|
return -1;
|
|
|
|
int64_t size;
|
|
if (!nsHttp::ParseInt64(slash, &size))
|
|
size = UINT64_MAX;
|
|
return size;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsHttpResponseHead <private>
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void
|
|
nsHttpResponseHead::ParseVersion(const char *str)
|
|
{
|
|
// Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
|
|
|
|
LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str));
|
|
|
|
// make sure we have HTTP at the beginning
|
|
if (PL_strncasecmp(str, "HTTP", 4) != 0) {
|
|
if (PL_strncasecmp(str, "ICY ", 4) == 0) {
|
|
// ShoutCast ICY is HTTP/1.0-like. Assume it is HTTP/1.0.
|
|
LOG(("Treating ICY as HTTP 1.0\n"));
|
|
mVersion = NS_HTTP_VERSION_1_0;
|
|
return;
|
|
}
|
|
LOG(("looks like a HTTP/0.9 response\n"));
|
|
mVersion = NS_HTTP_VERSION_0_9;
|
|
return;
|
|
}
|
|
str += 4;
|
|
|
|
if (*str != '/') {
|
|
LOG(("server did not send a version number; assuming HTTP/1.0\n"));
|
|
// NCSA/1.5.2 has a bug in which it fails to send a version number
|
|
// if the request version is HTTP/1.1, so we fall back on HTTP/1.0
|
|
mVersion = NS_HTTP_VERSION_1_0;
|
|
return;
|
|
}
|
|
|
|
char *p = PL_strchr(str, '.');
|
|
if (p == nullptr) {
|
|
LOG(("mal-formed server version; assuming HTTP/1.0\n"));
|
|
mVersion = NS_HTTP_VERSION_1_0;
|
|
return;
|
|
}
|
|
|
|
++p; // let b point to the minor version
|
|
|
|
int major = atoi(str + 1);
|
|
int minor = atoi(p);
|
|
|
|
if ((major > 2) || ((major == 2) && (minor >= 0)))
|
|
mVersion = NS_HTTP_VERSION_2_0;
|
|
else if ((major == 1) && (minor >= 1))
|
|
// at least HTTP/1.1
|
|
mVersion = NS_HTTP_VERSION_1_1;
|
|
else
|
|
// treat anything else as version 1.0
|
|
mVersion = NS_HTTP_VERSION_1_0;
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::ParseCacheControl(const char *val)
|
|
{
|
|
if (!(val && *val)) {
|
|
// clear flags
|
|
mCacheControlPrivate = false;
|
|
mCacheControlNoCache = false;
|
|
mCacheControlNoStore = false;
|
|
mCacheControlImmutable = false;
|
|
return;
|
|
}
|
|
|
|
// search header value for occurrence of "private"
|
|
if (nsHttp::FindToken(val, "private", HTTP_HEADER_VALUE_SEPS))
|
|
mCacheControlPrivate = true;
|
|
|
|
// search header value for occurrence(s) of "no-cache" but ignore
|
|
// occurrence(s) of "no-cache=blah"
|
|
if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS))
|
|
mCacheControlNoCache = true;
|
|
|
|
// search header value for occurrence of "no-store"
|
|
if (nsHttp::FindToken(val, "no-store", HTTP_HEADER_VALUE_SEPS))
|
|
mCacheControlNoStore = true;
|
|
|
|
// search header value for occurrence of "immutable"
|
|
if (nsHttp::FindToken(val, "immutable", HTTP_HEADER_VALUE_SEPS)) {
|
|
mCacheControlImmutable = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
nsHttpResponseHead::ParsePragma(const char *val)
|
|
{
|
|
LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val));
|
|
|
|
if (!(val && *val)) {
|
|
// clear no-cache flag
|
|
mPragmaNoCache = false;
|
|
return;
|
|
}
|
|
|
|
// Although 'Pragma: no-cache' is not a standard HTTP response header (it's
|
|
// a request header), caching is inhibited when this header is present so
|
|
// as to match existing Navigator behavior.
|
|
if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS))
|
|
mPragmaNoCache = true;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::VisitHeaders(nsIHttpHeaderVisitor *visitor,
|
|
nsHttpHeaderArray::VisitorFilter filter)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
mInVisitHeaders = true;
|
|
nsresult rv = mHeaders.VisitHeaders(visitor, filter);
|
|
mInVisitHeaders = false;
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsHttpResponseHead::GetOriginalHeader(nsHttpAtom aHeader,
|
|
nsIHttpHeaderVisitor *aVisitor)
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
mInVisitHeaders = true;
|
|
nsresult rv = mHeaders.GetOriginalHeader(aHeader, aVisitor);
|
|
mInVisitHeaders = false;
|
|
return rv;
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::HasContentType()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return !mContentType.IsEmpty();
|
|
}
|
|
|
|
bool
|
|
nsHttpResponseHead::HasContentCharset()
|
|
{
|
|
ReentrantMonitorAutoEnter monitor(mReentrantMonitor);
|
|
return !mContentCharset.IsEmpty();
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|