Back to index

lightning-sunbird  0.9+nobinonly
nsHttpResponseHead.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:set ts=4 sw=4 sts=4 et cin: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is Mozilla.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications.
00020  * Portions created by the Initial Developer are Copyright (C) 2001
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Darin Fisher <darin@netscape.com> (original author)
00025  *   Andreas M. Schneider <clarence@clarence.de>
00026  *   Christian Biesinger <cbiesinger@web.de>
00027  *
00028  * Alternatively, the contents of this file may be used under the terms of
00029  * either the GNU General Public License Version 2 or later (the "GPL"), or
00030  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00031  * in which case the provisions of the GPL or the LGPL are applicable instead
00032  * of those above. If you wish to allow use of your version of this file only
00033  * under the terms of either the GPL or the LGPL, and not to allow others to
00034  * use your version of this file under the terms of the MPL, indicate your
00035  * decision by deleting the provisions above and replace them with the notice
00036  * and other provisions required by the GPL or the LGPL. If you do not delete
00037  * the provisions above, a recipient may use your version of this file under
00038  * the terms of any one of the MPL, the GPL or the LGPL.
00039  *
00040  * ***** END LICENSE BLOCK ***** */
00041 
00042 #include <stdlib.h>
00043 #include "nsHttpResponseHead.h"
00044 #include "nsPrintfCString.h"
00045 #include "prprf.h"
00046 #include "prtime.h"
00047 #include "nsCRT.h"
00048 
00049 //-----------------------------------------------------------------------------
00050 // nsHttpResponseHead <public>
00051 //-----------------------------------------------------------------------------
00052 
00053 nsresult
00054 nsHttpResponseHead::SetHeader(nsHttpAtom hdr,
00055                               const nsACString &val,
00056                               PRBool merge)
00057 {
00058     nsresult rv = mHeaders.SetHeader(hdr, val, merge);
00059     if (NS_FAILED(rv)) return rv;
00060 
00061     // respond to changes in these headers.  we need to reparse the entire
00062     // header since the change may have merged in additional values.
00063     if (hdr == nsHttp::Cache_Control)
00064         ParseCacheControl(mHeaders.PeekHeader(hdr));
00065     else if (hdr == nsHttp::Pragma)
00066         ParsePragma(mHeaders.PeekHeader(hdr));
00067 
00068     return NS_OK;
00069 }
00070 
00071 void
00072 nsHttpResponseHead::SetContentLength(PRInt64 len)
00073 {
00074     mContentLength = len;
00075     if (!LL_GE_ZERO(len)) // < 0
00076         mHeaders.ClearHeader(nsHttp::Content_Length);
00077     else
00078         mHeaders.SetHeader(nsHttp::Content_Length, nsPrintfCString(20, "%lld", len));
00079 }
00080 
00081 void
00082 nsHttpResponseHead::Flatten(nsACString &buf, PRBool pruneTransients)
00083 {
00084     if (mVersion == NS_HTTP_VERSION_0_9)
00085         return;
00086 
00087     buf.AppendLiteral("HTTP/");
00088     if (mVersion == NS_HTTP_VERSION_1_1)
00089         buf.AppendLiteral("1.1 ");
00090     else
00091         buf.AppendLiteral("1.0 ");
00092 
00093     buf.Append(nsPrintfCString("%u", PRUintn(mStatus)) +
00094                NS_LITERAL_CSTRING(" ") +
00095                mStatusText +
00096                NS_LITERAL_CSTRING("\r\n"));
00097 
00098     if (!pruneTransients) {
00099         mHeaders.Flatten(buf, PR_FALSE);
00100         return;
00101     }
00102 
00103     // otherwise, we need to iterate over the headers and only flatten
00104     // those that are appropriate.
00105     PRUint32 i, count = mHeaders.Count();
00106     for (i=0; i<count; ++i) {
00107         nsHttpAtom header;
00108         const char *value = mHeaders.PeekHeaderAt(i, header);
00109 
00110         if (!value || header == nsHttp::Connection
00111                    || header == nsHttp::Proxy_Connection
00112                    || header == nsHttp::Keep_Alive
00113                    || header == nsHttp::WWW_Authenticate
00114                    || header == nsHttp::Proxy_Authenticate
00115                    || header == nsHttp::Trailer
00116                    || header == nsHttp::Transfer_Encoding
00117                    || header == nsHttp::Upgrade
00118                    // XXX this will cause problems when we start honoring
00119                    // Cache-Control: no-cache="set-cookie", what to do?
00120                    || header == nsHttp::Set_Cookie)
00121             continue;
00122 
00123         // otherwise, write out the "header: value\r\n" line
00124         buf.Append(nsDependentCString(header.get()) +
00125                    NS_LITERAL_CSTRING(": ") +
00126                    nsDependentCString(value) +
00127                    NS_LITERAL_CSTRING("\r\n"));
00128     }
00129 }
00130 
00131 nsresult
00132 nsHttpResponseHead::Parse(char *block)
00133 {
00134 
00135     LOG(("nsHttpResponseHead::Parse [this=%x]\n", this));
00136 
00137     // this command works on a buffer as prepared by Flatten, as such it is
00138     // not very forgiving ;-)
00139 
00140     char *p = PL_strstr(block, "\r\n");
00141     if (!p)
00142         return NS_ERROR_UNEXPECTED;
00143 
00144     *p = 0;
00145     ParseStatusLine(block);
00146 
00147     do {
00148         block = p + 2;
00149 
00150               if (*block == 0)
00151                      break;
00152 
00153         p = PL_strstr(block, "\r\n");
00154         if (!p)
00155             return NS_ERROR_UNEXPECTED;
00156 
00157         *p = 0;
00158         ParseHeaderLine(block);
00159 
00160     } while (1);
00161 
00162     return NS_OK;
00163 }
00164 
00165 void
00166 nsHttpResponseHead::ParseStatusLine(char *line)
00167 {
00168     //
00169     // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
00170     //
00171  
00172     // HTTP-Version
00173     ParseVersion(line);
00174     
00175     if ((mVersion == NS_HTTP_VERSION_0_9) || !(line = PL_strchr(line, ' '))) {
00176         mStatus = 200;
00177         mStatusText.AssignLiteral("OK");
00178     }
00179     else {
00180         // Status-Code
00181         mStatus = (PRUint16) atoi(++line);
00182         if (mStatus == 0) {
00183             LOG(("mal-formed response status; assuming status = 200\n"));
00184             mStatus = 200;
00185         }
00186 
00187         // Reason-Phrase is whatever is remaining of the line
00188         if (!(line = PL_strchr(line, ' '))) {
00189             LOG(("mal-formed response status line; assuming statusText = 'OK'\n"));
00190             mStatusText.AssignLiteral("OK");
00191         }
00192         else
00193             mStatusText = ++line;
00194     }
00195 
00196     LOG(("Have status line [version=%u status=%u statusText=%s]\n",
00197         PRUintn(mVersion), PRUintn(mStatus), mStatusText.get()));
00198 }
00199 
00200 void
00201 nsHttpResponseHead::ParseHeaderLine(char *line)
00202 {
00203     nsHttpAtom hdr = {0};
00204     char *val;
00205 
00206     mHeaders.ParseHeaderLine(line, &hdr, &val);
00207     // leading and trailing LWS has been removed from |val|
00208 
00209     // handle some special case headers...
00210     if (hdr == nsHttp::Content_Length) {
00211         PRInt64 len;
00212         // permit only a single value here.
00213         if (nsHttp::ParseInt64(val, &len))
00214             mContentLength = len;
00215         else
00216             LOG(("invalid content-length!\n"));
00217     }
00218     else if (hdr == nsHttp::Content_Type) {
00219         LOG(("ParseContentType [type=%s]\n", val));
00220         PRBool dummy;
00221         net_ParseContentType(nsDependentCString(val),
00222                              mContentType, mContentCharset, &dummy);
00223     }
00224     else if (hdr == nsHttp::Cache_Control)
00225         ParseCacheControl(val);
00226     else if (hdr == nsHttp::Pragma)
00227         ParsePragma(val);
00228 }
00229 
00230 // From section 13.2.3 of RFC2616, we compute the current age of a cached
00231 // response as follows:
00232 //
00233 //    currentAge = max(max(0, responseTime - dateValue), ageValue)
00234 //               + now - requestTime
00235 //
00236 //    where responseTime == now
00237 //
00238 // This is typically a very small number.
00239 //
00240 nsresult
00241 nsHttpResponseHead::ComputeCurrentAge(PRUint32 now,
00242                                       PRUint32 requestTime,
00243                                       PRUint32 *result)
00244 {
00245     PRUint32 dateValue;
00246     PRUint32 ageValue;
00247 
00248     *result = 0;
00249 
00250     if (NS_FAILED(GetDateValue(&dateValue))) {
00251         LOG(("nsHttpResponseHead::ComputeCurrentAge [this=%x] "
00252              "Date response header not set!\n", this));
00253         // Assume we have a fast connection and that our clock
00254         // is in sync with the server.
00255         dateValue = now;
00256     }
00257 
00258     // Compute apparent age
00259     if (now > dateValue)
00260         *result = now - dateValue;
00261 
00262     // Compute corrected received age
00263     if (NS_SUCCEEDED(GetAgeValue(&ageValue)))
00264         *result = PR_MAX(*result, ageValue);
00265 
00266     NS_ASSERTION(now >= requestTime, "bogus request time");
00267 
00268     // Compute current age
00269     *result += (now - requestTime);
00270     return NS_OK;
00271 }
00272 
00273 // From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
00274 // response as follows:
00275 //
00276 //     freshnessLifetime = max_age_value
00277 // <or>
00278 //     freshnessLifetime = expires_value - date_value
00279 // <or>
00280 //     freshnessLifetime = (date_value - last_modified_value) * 0.10
00281 // <or>
00282 //     freshnessLifetime = 0
00283 //
00284 nsresult
00285 nsHttpResponseHead::ComputeFreshnessLifetime(PRUint32 *result)
00286 {
00287     *result = 0;
00288 
00289     // Try HTTP/1.1 style max-age directive...
00290     if (NS_SUCCEEDED(GetMaxAgeValue(result)))
00291         return NS_OK;
00292 
00293     *result = 0;
00294 
00295     PRUint32 date = 0, date2 = 0;
00296     if (NS_FAILED(GetDateValue(&date)))
00297         date = NowInSeconds(); // synthesize a date header if none exists
00298 
00299     // Try HTTP/1.0 style expires header...
00300     if (NS_SUCCEEDED(GetExpiresValue(&date2))) {
00301         if (date2 > date)
00302             *result = date2 - date;
00303         // the Expires header can specify a date in the past.
00304         return NS_OK;
00305     }
00306     
00307     // Fallback on heuristic using last modified header...
00308     if (NS_SUCCEEDED(GetLastModifiedValue(&date2))) {
00309         LOG(("using last-modified to determine freshness-lifetime\n"));
00310         LOG(("last-modified = %u, date = %u\n", date2, date));
00311         if (date2 <= date) {
00312             // this only makes sense if last-modified is actually in the past
00313             *result = (date - date2) / 10;
00314             return NS_OK;
00315         }
00316     }
00317 
00318     // These responses can be cached indefinitely.
00319     if ((mStatus == 300) || (mStatus == 301)) {
00320         *result = PRUint32(-1);
00321         return NS_OK;
00322     }
00323 
00324     LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %x] "
00325          "Insufficient information to compute a non-zero freshness "
00326          "lifetime!\n", this));
00327 
00328     return NS_OK;
00329 }
00330 
00331 PRBool
00332 nsHttpResponseHead::MustValidate()
00333 {
00334     LOG(("nsHttpResponseHead::MustValidate ??\n"));
00335 
00336     // Some response codes are cacheable, but the rest are not.  This switch
00337     // should stay in sync with the list in nsHttpChannel::ProcessResponse
00338     switch (mStatus) {
00339         // Success codes
00340     case 200:
00341     case 203:
00342     case 206:
00343         // Cacheable redirects
00344     case 300:
00345     case 301:
00346     case 302:
00347     case 304:
00348     case 307:
00349         break;
00350         // Uncacheable redirects
00351     case 303:
00352     case 305:
00353         // Other known errors
00354     case 401:
00355     case 407:
00356     case 412:
00357     case 416:
00358     default:  // revalidate unknown error pages
00359         LOG(("Must validate since response is an uncacheable error page\n"));
00360         return PR_TRUE;
00361     }
00362     
00363     // The no-cache response header indicates that we must validate this
00364     // cached response before reusing.
00365     if (NoCache()) {
00366         LOG(("Must validate since response contains 'no-cache' header\n"));
00367         return PR_TRUE;
00368     }
00369 
00370     // Likewise, if the response is no-store, then we must validate this
00371     // cached response before reusing.  NOTE: it may seem odd that a no-store
00372     // response may be cached, but indeed all responses are cached in order
00373     // to support File->SaveAs, View->PageSource, and other browser features.
00374     if (NoStore()) {
00375         LOG(("Must validate since response contains 'no-store' header\n"));
00376         return PR_TRUE;
00377     }
00378 
00379     // Compare the Expires header to the Date header.  If the server sent an
00380     // Expires header with a timestamp in the past, then we must validate this
00381     // cached response before reusing.
00382     if (ExpiresInPast()) {
00383         LOG(("Must validate since Expires < Date\n"));
00384         return PR_TRUE;
00385     }
00386 
00387     LOG(("no mandatory validation requirement\n"));
00388     return PR_FALSE;
00389 }
00390 
00391 PRBool
00392 nsHttpResponseHead::MustValidateIfExpired()
00393 {
00394     // according to RFC2616, section 14.9.4:
00395     //
00396     //  When the must-revalidate directive is present in a response received by a   
00397     //  cache, that cache MUST NOT use the entry after it becomes stale to respond to 
00398     //  a subsequent request without first revalidating it with the origin server.
00399     //
00400     const char *val = PeekHeader(nsHttp::Cache_Control);
00401     return val && PL_strcasestr(val, "must-revalidate");
00402 }
00403 
00404 PRBool
00405 nsHttpResponseHead::IsResumable()
00406 {
00407     // even though some HTTP/1.0 servers may support byte range requests, we're not
00408     // going to bother with them, since those servers wouldn't understand If-Range.
00409     return mVersion >= NS_HTTP_VERSION_1_1 &&
00410            PeekHeader(nsHttp::Content_Length) && 
00411           (PeekHeader(nsHttp::ETag) || PeekHeader(nsHttp::Last_Modified)) &&
00412            PL_strcasestr(PeekHeader(nsHttp::Accept_Ranges), "bytes");
00413 }
00414 
00415 PRBool
00416 nsHttpResponseHead::ExpiresInPast()
00417 {
00418     PRUint32 expiresVal, dateVal;
00419     return NS_SUCCEEDED(GetExpiresValue(&expiresVal)) &&
00420            NS_SUCCEEDED(GetDateValue(&dateVal)) &&
00421            expiresVal < dateVal;
00422 }
00423 
00424 nsresult
00425 nsHttpResponseHead::UpdateHeaders(nsHttpHeaderArray &headers)
00426 {
00427     LOG(("nsHttpResponseHead::UpdateHeaders [this=%x]\n", this));
00428 
00429     PRUint32 i, count = headers.Count();
00430     for (i=0; i<count; ++i) {
00431         nsHttpAtom header;
00432         const char *val = headers.PeekHeaderAt(i, header);
00433 
00434         if (!val) {
00435             NS_NOTREACHED("null header value");
00436             continue;
00437         }
00438 
00439         // Ignore any hop-by-hop headers...
00440         if (header == nsHttp::Connection          ||
00441             header == nsHttp::Proxy_Connection    ||
00442             header == nsHttp::Keep_Alive          ||
00443             header == nsHttp::Proxy_Authenticate  ||
00444             header == nsHttp::Proxy_Authorization || // not a response header!
00445             header == nsHttp::TE                  ||
00446             header == nsHttp::Trailer             ||
00447             header == nsHttp::Transfer_Encoding   ||
00448             header == nsHttp::Upgrade             ||
00449         // Ignore any non-modifiable headers...
00450             header == nsHttp::Content_Location    ||
00451             header == nsHttp::Content_MD5         ||
00452             header == nsHttp::ETag                ||
00453         // Assume Cache-Control: "no-transform"
00454             header == nsHttp::Content_Encoding    ||
00455             header == nsHttp::Content_Range       ||
00456             header == nsHttp::Content_Type        ||
00457         // Ignore wacky headers too...
00458             // this one is for MS servers that send "Content-Length: 0"
00459             // on 304 responses
00460             header == nsHttp::Content_Length) {
00461             LOG(("ignoring response header [%s: %s]\n", header.get(), val));
00462         }
00463         else {
00464             LOG(("new response header [%s: %s]\n", header.get(), val));
00465 
00466             // overwrite the current header value with the new value...
00467             SetHeader(header, nsDependentCString(val));
00468         }
00469     }
00470 
00471     return NS_OK;
00472 }
00473 
00474 void
00475 nsHttpResponseHead::Reset()
00476 {
00477     LOG(("nsHttpResponseHead::Reset\n"));
00478 
00479     ClearHeaders();
00480 
00481     mVersion = NS_HTTP_VERSION_1_1;
00482     mStatus = 200;
00483     mContentLength = LL_MAXUINT;
00484     mCacheControlNoStore = PR_FALSE;
00485     mCacheControlNoCache = PR_FALSE;
00486     mPragmaNoCache = PR_FALSE;
00487     mStatusText.Truncate();
00488     mContentType.Truncate();
00489     mContentCharset.Truncate();
00490 }
00491 
00492 nsresult
00493 nsHttpResponseHead::ParseDateHeader(nsHttpAtom header, PRUint32 *result)
00494 {
00495     const char *val = PeekHeader(header);
00496     if (!val)
00497         return NS_ERROR_NOT_AVAILABLE;
00498 
00499     PRTime time;
00500     PRStatus st = PR_ParseTimeString(val, PR_TRUE, &time);
00501     if (st != PR_SUCCESS)
00502         return NS_ERROR_NOT_AVAILABLE;
00503 
00504     *result = PRTimeToSeconds(time); 
00505     return NS_OK;
00506 }
00507 
00508 nsresult
00509 nsHttpResponseHead::GetAgeValue(PRUint32 *result)
00510 {
00511     const char *val = PeekHeader(nsHttp::Age);
00512     if (!val)
00513         return NS_ERROR_NOT_AVAILABLE;
00514 
00515     *result = (PRUint32) atoi(val);
00516     return NS_OK;
00517 }
00518 
00519 // Return the value of the (HTTP 1.1) max-age directive, which itself is a
00520 // component of the Cache-Control response header
00521 nsresult
00522 nsHttpResponseHead::GetMaxAgeValue(PRUint32 *result)
00523 {
00524     const char *val = PeekHeader(nsHttp::Cache_Control);
00525     if (!val)
00526         return NS_ERROR_NOT_AVAILABLE;
00527 
00528     const char *p = PL_strcasestr(val, "max-age=");
00529     if (!p)
00530         return NS_ERROR_NOT_AVAILABLE;
00531 
00532     *result = (PRUint32) atoi(p + 8);
00533     return NS_OK;
00534 }
00535 
00536 nsresult
00537 nsHttpResponseHead::GetExpiresValue(PRUint32 *result)
00538 {
00539     const char *val = PeekHeader(nsHttp::Expires);
00540     if (!val)
00541         return NS_ERROR_NOT_AVAILABLE;
00542 
00543     PRTime time;
00544     PRStatus st = PR_ParseTimeString(val, PR_TRUE, &time);
00545     if (st != PR_SUCCESS) {
00546         // parsing failed... RFC 2616 section 14.21 says we should treat this
00547         // as an expiration time in the past.
00548         *result = 0;
00549         return NS_OK;
00550     }
00551 
00552     if (LL_CMP(time, <, LL_Zero()))
00553         *result = 0;
00554     else
00555         *result = PRTimeToSeconds(time); 
00556     return NS_OK;
00557 }
00558 
00559 PRInt64
00560 nsHttpResponseHead::TotalEntitySize()
00561 {
00562     const char* contentRange = PeekHeader(nsHttp::Content_Range);
00563     if (!contentRange)
00564         return ContentLength();
00565 
00566     // Total length is after a slash
00567     const char* slash = strrchr(contentRange, '/');
00568     if (!slash)
00569         return -1; // No idea what the length is
00570 
00571     slash++;
00572     if (*slash == '*') // Server doesn't know the length
00573         return -1;
00574 
00575     PRInt64 size;
00576     if (!nsHttp::ParseInt64(slash, &size))
00577         size = LL_MAXUINT;
00578     return size;
00579 }
00580 
00581 //-----------------------------------------------------------------------------
00582 // nsHttpResponseHead <private>
00583 //-----------------------------------------------------------------------------
00584 
00585 void
00586 nsHttpResponseHead::ParseVersion(const char *str)
00587 {
00588     // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
00589 
00590     LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str));
00591 
00592     // make sure we have HTTP at the beginning
00593     if (PL_strncasecmp(str, "HTTP", 4) != 0) {
00594         LOG(("looks like a HTTP/0.9 response\n"));
00595         mVersion = NS_HTTP_VERSION_0_9;
00596         return;
00597     }
00598     str += 4;
00599 
00600     if (*str != '/') {
00601         LOG(("server did not send a version number; assuming HTTP/1.0\n"));
00602         // NCSA/1.5.2 has a bug in which it fails to send a version number
00603         // if the request version is HTTP/1.1, so we fall back on HTTP/1.0
00604         mVersion = NS_HTTP_VERSION_1_0;
00605         return;
00606     }
00607 
00608     char *p = PL_strchr(str, '.');
00609     if (p == nsnull) {
00610         LOG(("mal-formed server version; assuming HTTP/1.0\n"));
00611         mVersion = NS_HTTP_VERSION_1_0;
00612         return;
00613     }
00614 
00615     ++p; // let b point to the minor version
00616 
00617     int major = atoi(str + 1);
00618     int minor = atoi(p);
00619 
00620     if ((major > 1) || ((major == 1) && (minor >= 1)))
00621         // at least HTTP/1.1
00622         mVersion = NS_HTTP_VERSION_1_1;
00623     else
00624         // treat anything else as version 1.0
00625         mVersion = NS_HTTP_VERSION_1_0;
00626 }
00627 
00628 void
00629 nsHttpResponseHead::ParseCacheControl(const char *val)
00630 {
00631     if (!(val && *val)) {
00632         // clear flags
00633         mCacheControlNoCache = PR_FALSE;
00634         mCacheControlNoStore = PR_FALSE;
00635         return;
00636     }
00637 
00638     const char *s = val;
00639 
00640     // search header value for occurance(s) of "no-cache" but ignore
00641     // occurance(s) of "no-cache=blah"
00642     while ((s = PL_strcasestr(s, "no-cache")) != nsnull) {
00643         s += (sizeof("no-cache") - 1);
00644         if (*s != '=')
00645             mCacheControlNoCache = PR_TRUE;
00646     }
00647 
00648     // search header value for occurance of "no-store" 
00649     if (PL_strcasestr(val, "no-store"))
00650         mCacheControlNoStore = PR_TRUE;
00651 }
00652 
00653 void
00654 nsHttpResponseHead::ParsePragma(const char *val)
00655 {
00656     LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val));
00657 
00658     if (!(val && *val)) {
00659         // clear no-cache flag
00660         mPragmaNoCache = PR_FALSE;
00661         return;
00662     }
00663 
00664     // Although 'Pragma: no-cache' is not a standard HTTP response header (it's
00665     // a request header), caching is inhibited when this header is present so
00666     // as to match existing Navigator behavior.
00667     if (PL_strcasestr(val, "no-cache"))
00668         mPragmaNoCache = PR_TRUE;
00669 }