Bug 458256. Use LoadLibraryW instead of LoadLibrary (patch by DougT). r+sr=vlad
[wine-gecko.git] / netwerk / protocol / http / src / nsHttpResponseHead.cpp
bloba1aef490c0091d29ea763f7a9fd83c522af91587
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 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is Mozilla.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications.
20 * Portions created by the Initial Developer are Copyright (C) 2001
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Darin Fisher <darin@netscape.com> (original author)
25 * Andreas M. Schneider <clarence@clarence.de>
26 * Christian Biesinger <cbiesinger@web.de>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either the GNU General Public License Version 2 or later (the "GPL"), or
30 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
42 #include <stdlib.h>
43 #include "nsHttpResponseHead.h"
44 #include "nsPrintfCString.h"
45 #include "prprf.h"
46 #include "prtime.h"
47 #include "nsCRT.h"
49 //-----------------------------------------------------------------------------
50 // nsHttpResponseHead <public>
51 //-----------------------------------------------------------------------------
53 nsresult
54 nsHttpResponseHead::SetHeader(nsHttpAtom hdr,
55 const nsACString &val,
56 PRBool merge)
58 nsresult rv = mHeaders.SetHeader(hdr, val, merge);
59 if (NS_FAILED(rv)) return rv;
61 // respond to changes in these headers. we need to reparse the entire
62 // header since the change may have merged in additional values.
63 if (hdr == nsHttp::Cache_Control)
64 ParseCacheControl(mHeaders.PeekHeader(hdr));
65 else if (hdr == nsHttp::Pragma)
66 ParsePragma(mHeaders.PeekHeader(hdr));
68 return NS_OK;
71 void
72 nsHttpResponseHead::SetContentLength(PRInt64 len)
74 mContentLength = len;
75 if (!LL_GE_ZERO(len)) // < 0
76 mHeaders.ClearHeader(nsHttp::Content_Length);
77 else
78 mHeaders.SetHeader(nsHttp::Content_Length, nsPrintfCString(20, "%lld", len));
81 void
82 nsHttpResponseHead::Flatten(nsACString &buf, PRBool pruneTransients)
84 if (mVersion == NS_HTTP_VERSION_0_9)
85 return;
87 buf.AppendLiteral("HTTP/");
88 if (mVersion == NS_HTTP_VERSION_1_1)
89 buf.AppendLiteral("1.1 ");
90 else
91 buf.AppendLiteral("1.0 ");
93 buf.Append(nsPrintfCString("%u", PRUintn(mStatus)) +
94 NS_LITERAL_CSTRING(" ") +
95 mStatusText +
96 NS_LITERAL_CSTRING("\r\n"));
98 if (!pruneTransients) {
99 mHeaders.Flatten(buf, PR_FALSE);
100 return;
103 // otherwise, we need to iterate over the headers and only flatten
104 // those that are appropriate.
105 PRUint32 i, count = mHeaders.Count();
106 for (i=0; i<count; ++i) {
107 nsHttpAtom header;
108 const char *value = mHeaders.PeekHeaderAt(i, header);
110 if (!value || header == nsHttp::Connection
111 || header == nsHttp::Proxy_Connection
112 || header == nsHttp::Keep_Alive
113 || header == nsHttp::WWW_Authenticate
114 || header == nsHttp::Proxy_Authenticate
115 || header == nsHttp::Trailer
116 || header == nsHttp::Transfer_Encoding
117 || header == nsHttp::Upgrade
118 // XXX this will cause problems when we start honoring
119 // Cache-Control: no-cache="set-cookie", what to do?
120 || header == nsHttp::Set_Cookie)
121 continue;
123 // otherwise, write out the "header: value\r\n" line
124 buf.Append(nsDependentCString(header.get()) +
125 NS_LITERAL_CSTRING(": ") +
126 nsDependentCString(value) +
127 NS_LITERAL_CSTRING("\r\n"));
131 nsresult
132 nsHttpResponseHead::Parse(char *block)
135 LOG(("nsHttpResponseHead::Parse [this=%x]\n", this));
137 // this command works on a buffer as prepared by Flatten, as such it is
138 // not very forgiving ;-)
140 char *p = PL_strstr(block, "\r\n");
141 if (!p)
142 return NS_ERROR_UNEXPECTED;
144 *p = 0;
145 ParseStatusLine(block);
147 do {
148 block = p + 2;
150 if (*block == 0)
151 break;
153 p = PL_strstr(block, "\r\n");
154 if (!p)
155 return NS_ERROR_UNEXPECTED;
157 *p = 0;
158 ParseHeaderLine(block);
160 } while (1);
162 return NS_OK;
165 void
166 nsHttpResponseHead::ParseStatusLine(char *line)
169 // Parse Status-Line:: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
172 // HTTP-Version
173 ParseVersion(line);
175 if ((mVersion == NS_HTTP_VERSION_0_9) || !(line = PL_strchr(line, ' '))) {
176 mStatus = 200;
177 mStatusText.AssignLiteral("OK");
179 else {
180 // Status-Code
181 mStatus = (PRUint16) atoi(++line);
182 if (mStatus == 0) {
183 LOG(("mal-formed response status; assuming status = 200\n"));
184 mStatus = 200;
187 // Reason-Phrase is whatever is remaining of the line
188 if (!(line = PL_strchr(line, ' '))) {
189 LOG(("mal-formed response status line; assuming statusText = 'OK'\n"));
190 mStatusText.AssignLiteral("OK");
192 else
193 mStatusText = ++line;
196 LOG(("Have status line [version=%u status=%u statusText=%s]\n",
197 PRUintn(mVersion), PRUintn(mStatus), mStatusText.get()));
200 void
201 nsHttpResponseHead::ParseHeaderLine(char *line)
203 nsHttpAtom hdr = {0};
204 char *val;
206 mHeaders.ParseHeaderLine(line, &hdr, &val);
207 // leading and trailing LWS has been removed from |val|
209 // handle some special case headers...
210 if (hdr == nsHttp::Content_Length) {
211 PRInt64 len;
212 // permit only a single value here.
213 if (nsHttp::ParseInt64(val, &len))
214 mContentLength = len;
215 else
216 LOG(("invalid content-length!\n"));
218 else if (hdr == nsHttp::Content_Type) {
219 LOG(("ParseContentType [type=%s]\n", val));
220 PRBool dummy;
221 net_ParseContentType(nsDependentCString(val),
222 mContentType, mContentCharset, &dummy);
224 else if (hdr == nsHttp::Cache_Control)
225 ParseCacheControl(val);
226 else if (hdr == nsHttp::Pragma)
227 ParsePragma(val);
230 // From section 13.2.3 of RFC2616, we compute the current age of a cached
231 // response as follows:
233 // currentAge = max(max(0, responseTime - dateValue), ageValue)
234 // + now - requestTime
236 // where responseTime == now
238 // This is typically a very small number.
240 nsresult
241 nsHttpResponseHead::ComputeCurrentAge(PRUint32 now,
242 PRUint32 requestTime,
243 PRUint32 *result)
245 PRUint32 dateValue;
246 PRUint32 ageValue;
248 *result = 0;
250 if (NS_FAILED(GetDateValue(&dateValue))) {
251 LOG(("nsHttpResponseHead::ComputeCurrentAge [this=%x] "
252 "Date response header not set!\n", this));
253 // Assume we have a fast connection and that our clock
254 // is in sync with the server.
255 dateValue = now;
258 // Compute apparent age
259 if (now > dateValue)
260 *result = now - dateValue;
262 // Compute corrected received age
263 if (NS_SUCCEEDED(GetAgeValue(&ageValue)))
264 *result = PR_MAX(*result, ageValue);
266 NS_ASSERTION(now >= requestTime, "bogus request time");
268 // Compute current age
269 *result += (now - requestTime);
270 return NS_OK;
273 // From section 13.2.4 of RFC2616, we compute the freshness lifetime of a cached
274 // response as follows:
276 // freshnessLifetime = max_age_value
277 // <or>
278 // freshnessLifetime = expires_value - date_value
279 // <or>
280 // freshnessLifetime = (date_value - last_modified_value) * 0.10
281 // <or>
282 // freshnessLifetime = 0
284 nsresult
285 nsHttpResponseHead::ComputeFreshnessLifetime(PRUint32 *result)
287 *result = 0;
289 // Try HTTP/1.1 style max-age directive...
290 if (NS_SUCCEEDED(GetMaxAgeValue(result)))
291 return NS_OK;
293 *result = 0;
295 PRUint32 date = 0, date2 = 0;
296 if (NS_FAILED(GetDateValue(&date)))
297 date = NowInSeconds(); // synthesize a date header if none exists
299 // Try HTTP/1.0 style expires header...
300 if (NS_SUCCEEDED(GetExpiresValue(&date2))) {
301 if (date2 > date)
302 *result = date2 - date;
303 // the Expires header can specify a date in the past.
304 return NS_OK;
307 // Fallback on heuristic using last modified header...
308 if (NS_SUCCEEDED(GetLastModifiedValue(&date2))) {
309 LOG(("using last-modified to determine freshness-lifetime\n"));
310 LOG(("last-modified = %u, date = %u\n", date2, date));
311 if (date2 <= date) {
312 // this only makes sense if last-modified is actually in the past
313 *result = (date - date2) / 10;
314 return NS_OK;
318 // These responses can be cached indefinitely.
319 if ((mStatus == 300) || (mStatus == 301)) {
320 *result = PRUint32(-1);
321 return NS_OK;
324 LOG(("nsHttpResponseHead::ComputeFreshnessLifetime [this = %x] "
325 "Insufficient information to compute a non-zero freshness "
326 "lifetime!\n", this));
328 return NS_OK;
331 PRBool
332 nsHttpResponseHead::MustValidate()
334 LOG(("nsHttpResponseHead::MustValidate ??\n"));
336 // Some response codes are cacheable, but the rest are not. This switch
337 // should stay in sync with the list in nsHttpChannel::ProcessResponse
338 switch (mStatus) {
339 // Success codes
340 case 200:
341 case 203:
342 case 206:
343 // Cacheable redirects
344 case 300:
345 case 301:
346 case 302:
347 case 304:
348 case 307:
349 break;
350 // Uncacheable redirects
351 case 303:
352 case 305:
353 // Other known errors
354 case 401:
355 case 407:
356 case 412:
357 case 416:
358 default: // revalidate unknown error pages
359 LOG(("Must validate since response is an uncacheable error page\n"));
360 return PR_TRUE;
363 // The no-cache response header indicates that we must validate this
364 // cached response before reusing.
365 if (NoCache()) {
366 LOG(("Must validate since response contains 'no-cache' header\n"));
367 return PR_TRUE;
370 // Likewise, if the response is no-store, then we must validate this
371 // cached response before reusing. NOTE: it may seem odd that a no-store
372 // response may be cached, but indeed all responses are cached in order
373 // to support File->SaveAs, View->PageSource, and other browser features.
374 if (NoStore()) {
375 LOG(("Must validate since response contains 'no-store' header\n"));
376 return PR_TRUE;
379 // Compare the Expires header to the Date header. If the server sent an
380 // Expires header with a timestamp in the past, then we must validate this
381 // cached response before reusing.
382 if (ExpiresInPast()) {
383 LOG(("Must validate since Expires < Date\n"));
384 return PR_TRUE;
387 LOG(("no mandatory validation requirement\n"));
388 return PR_FALSE;
391 PRBool
392 nsHttpResponseHead::MustValidateIfExpired()
394 // according to RFC2616, section 14.9.4:
396 // When the must-revalidate directive is present in a response received by a
397 // cache, that cache MUST NOT use the entry after it becomes stale to respond to
398 // a subsequent request without first revalidating it with the origin server.
400 return HasHeaderValue(nsHttp::Cache_Control, "must-revalidate");
403 PRBool
404 nsHttpResponseHead::IsResumable()
406 // even though some HTTP/1.0 servers may support byte range requests, we're not
407 // going to bother with them, since those servers wouldn't understand If-Range.
408 return mVersion >= NS_HTTP_VERSION_1_1 &&
409 PeekHeader(nsHttp::Content_Length) &&
410 (PeekHeader(nsHttp::ETag) || PeekHeader(nsHttp::Last_Modified)) &&
411 HasHeaderValue(nsHttp::Accept_Ranges, "bytes");
414 PRBool
415 nsHttpResponseHead::ExpiresInPast()
417 PRUint32 expiresVal, dateVal;
418 return NS_SUCCEEDED(GetExpiresValue(&expiresVal)) &&
419 NS_SUCCEEDED(GetDateValue(&dateVal)) &&
420 expiresVal < dateVal;
423 nsresult
424 nsHttpResponseHead::UpdateHeaders(nsHttpHeaderArray &headers)
426 LOG(("nsHttpResponseHead::UpdateHeaders [this=%x]\n", this));
428 PRUint32 i, count = headers.Count();
429 for (i=0; i<count; ++i) {
430 nsHttpAtom header;
431 const char *val = headers.PeekHeaderAt(i, header);
433 if (!val) {
434 NS_NOTREACHED("null header value");
435 continue;
438 // Ignore any hop-by-hop headers...
439 if (header == nsHttp::Connection ||
440 header == nsHttp::Proxy_Connection ||
441 header == nsHttp::Keep_Alive ||
442 header == nsHttp::Proxy_Authenticate ||
443 header == nsHttp::Proxy_Authorization || // not a response header!
444 header == nsHttp::TE ||
445 header == nsHttp::Trailer ||
446 header == nsHttp::Transfer_Encoding ||
447 header == nsHttp::Upgrade ||
448 // Ignore any non-modifiable headers...
449 header == nsHttp::Content_Location ||
450 header == nsHttp::Content_MD5 ||
451 header == nsHttp::ETag ||
452 // Assume Cache-Control: "no-transform"
453 header == nsHttp::Content_Encoding ||
454 header == nsHttp::Content_Range ||
455 header == nsHttp::Content_Type ||
456 // Ignore wacky headers too...
457 // this one is for MS servers that send "Content-Length: 0"
458 // on 304 responses
459 header == nsHttp::Content_Length) {
460 LOG(("ignoring response header [%s: %s]\n", header.get(), val));
462 else {
463 LOG(("new response header [%s: %s]\n", header.get(), val));
465 // overwrite the current header value with the new value...
466 SetHeader(header, nsDependentCString(val));
470 return NS_OK;
473 void
474 nsHttpResponseHead::Reset()
476 LOG(("nsHttpResponseHead::Reset\n"));
478 ClearHeaders();
480 mVersion = NS_HTTP_VERSION_1_1;
481 mStatus = 200;
482 mContentLength = LL_MAXUINT;
483 mCacheControlNoStore = PR_FALSE;
484 mCacheControlNoCache = PR_FALSE;
485 mCacheControlPublic = PR_FALSE;
486 mPragmaNoCache = PR_FALSE;
487 mStatusText.Truncate();
488 mContentType.Truncate();
489 mContentCharset.Truncate();
492 nsresult
493 nsHttpResponseHead::ParseDateHeader(nsHttpAtom header, PRUint32 *result)
495 const char *val = PeekHeader(header);
496 if (!val)
497 return NS_ERROR_NOT_AVAILABLE;
499 PRTime time;
500 PRStatus st = PR_ParseTimeString(val, PR_TRUE, &time);
501 if (st != PR_SUCCESS)
502 return NS_ERROR_NOT_AVAILABLE;
504 *result = PRTimeToSeconds(time);
505 return NS_OK;
508 nsresult
509 nsHttpResponseHead::GetAgeValue(PRUint32 *result)
511 const char *val = PeekHeader(nsHttp::Age);
512 if (!val)
513 return NS_ERROR_NOT_AVAILABLE;
515 *result = (PRUint32) atoi(val);
516 return NS_OK;
519 // Return the value of the (HTTP 1.1) max-age directive, which itself is a
520 // component of the Cache-Control response header
521 nsresult
522 nsHttpResponseHead::GetMaxAgeValue(PRUint32 *result)
524 const char *val = PeekHeader(nsHttp::Cache_Control);
525 if (!val)
526 return NS_ERROR_NOT_AVAILABLE;
528 const char *p = PL_strcasestr(val, "max-age=");
529 if (!p)
530 return NS_ERROR_NOT_AVAILABLE;
532 *result = (PRUint32) atoi(p + 8);
533 return NS_OK;
536 nsresult
537 nsHttpResponseHead::GetExpiresValue(PRUint32 *result)
539 const char *val = PeekHeader(nsHttp::Expires);
540 if (!val)
541 return NS_ERROR_NOT_AVAILABLE;
543 PRTime time;
544 PRStatus st = PR_ParseTimeString(val, PR_TRUE, &time);
545 if (st != PR_SUCCESS) {
546 // parsing failed... RFC 2616 section 14.21 says we should treat this
547 // as an expiration time in the past.
548 *result = 0;
549 return NS_OK;
552 if (LL_CMP(time, <, LL_Zero()))
553 *result = 0;
554 else
555 *result = PRTimeToSeconds(time);
556 return NS_OK;
559 PRInt64
560 nsHttpResponseHead::TotalEntitySize()
562 const char* contentRange = PeekHeader(nsHttp::Content_Range);
563 if (!contentRange)
564 return ContentLength();
566 // Total length is after a slash
567 const char* slash = strrchr(contentRange, '/');
568 if (!slash)
569 return -1; // No idea what the length is
571 slash++;
572 if (*slash == '*') // Server doesn't know the length
573 return -1;
575 PRInt64 size;
576 if (!nsHttp::ParseInt64(slash, &size))
577 size = LL_MAXUINT;
578 return size;
581 //-----------------------------------------------------------------------------
582 // nsHttpResponseHead <private>
583 //-----------------------------------------------------------------------------
585 void
586 nsHttpResponseHead::ParseVersion(const char *str)
588 // Parse HTTP-Version:: "HTTP" "/" 1*DIGIT "." 1*DIGIT
590 LOG(("nsHttpResponseHead::ParseVersion [version=%s]\n", str));
592 // make sure we have HTTP at the beginning
593 if (PL_strncasecmp(str, "HTTP", 4) != 0) {
594 LOG(("looks like a HTTP/0.9 response\n"));
595 mVersion = NS_HTTP_VERSION_0_9;
596 return;
598 str += 4;
600 if (*str != '/') {
601 LOG(("server did not send a version number; assuming HTTP/1.0\n"));
602 // NCSA/1.5.2 has a bug in which it fails to send a version number
603 // if the request version is HTTP/1.1, so we fall back on HTTP/1.0
604 mVersion = NS_HTTP_VERSION_1_0;
605 return;
608 char *p = PL_strchr(str, '.');
609 if (p == nsnull) {
610 LOG(("mal-formed server version; assuming HTTP/1.0\n"));
611 mVersion = NS_HTTP_VERSION_1_0;
612 return;
615 ++p; // let b point to the minor version
617 int major = atoi(str + 1);
618 int minor = atoi(p);
620 if ((major > 1) || ((major == 1) && (minor >= 1)))
621 // at least HTTP/1.1
622 mVersion = NS_HTTP_VERSION_1_1;
623 else
624 // treat anything else as version 1.0
625 mVersion = NS_HTTP_VERSION_1_0;
628 void
629 nsHttpResponseHead::ParseCacheControl(const char *val)
631 if (!(val && *val)) {
632 // clear flags
633 mCacheControlNoCache = PR_FALSE;
634 mCacheControlNoStore = PR_FALSE;
635 mCacheControlPublic = PR_FALSE;
636 return;
639 // search header value for occurrence(s) of "no-cache" but ignore
640 // occurrence(s) of "no-cache=blah"
641 if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS))
642 mCacheControlNoCache = PR_TRUE;
644 // search header value for occurrence of "no-store"
645 if (nsHttp::FindToken(val, "no-store", HTTP_HEADER_VALUE_SEPS))
646 mCacheControlNoStore = PR_TRUE;
648 // search header value for occurrence of "public"
649 if (nsHttp::FindToken(val, "public", HTTP_HEADER_VALUE_SEPS))
650 mCacheControlPublic = PR_TRUE;
653 void
654 nsHttpResponseHead::ParsePragma(const char *val)
656 LOG(("nsHttpResponseHead::ParsePragma [val=%s]\n", val));
658 if (!(val && *val)) {
659 // clear no-cache flag
660 mPragmaNoCache = PR_FALSE;
661 return;
664 // Although 'Pragma: no-cache' is not a standard HTTP response header (it's
665 // a request header), caching is inhibited when this header is present so
666 // as to match existing Navigator behavior.
667 if (nsHttp::FindToken(val, "no-cache", HTTP_HEADER_VALUE_SEPS))
668 mPragmaNoCache = PR_TRUE;