OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org) | |
3 * Copyright (C) 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. | |
4 * Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ | |
5 * Copyright (C) 2009 Google Inc. All rights reserved. | |
6 * Copyright (C) 2011 Apple Inc. All Rights Reserved. | |
7 * | |
8 * Redistribution and use in source and binary forms, with or without | |
9 * modification, are permitted provided that the following conditions | |
10 * are met: | |
11 * | |
12 * 1. Redistributions of source code must retain the above copyright | |
13 * notice, this list of conditions and the following disclaimer. | |
14 * 2. Redistributions in binary form must reproduce the above copyright | |
15 * notice, this list of conditions and the following disclaimer in the | |
16 * documentation and/or other materials provided with the distribution. | |
17 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of | |
18 * its contributors may be used to endorse or promote products derived | |
19 * from this software without specific prior written permission. | |
20 * | |
21 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY | |
22 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |
23 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
24 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY | |
25 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
26 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | |
28 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
31 */ | |
32 | |
33 #include "sky/engine/platform/network/HTTPParsers.h" | |
34 | |
35 #include "sky/engine/wtf/DateMath.h" | |
36 #include "sky/engine/wtf/MathExtras.h" | |
37 #include "sky/engine/wtf/text/CString.h" | |
38 #include "sky/engine/wtf/text/StringBuilder.h" | |
39 #include "sky/engine/wtf/text/WTFString.h" | |
40 #include "sky/engine/wtf/unicode/CharacterNames.h" | |
41 | |
42 using namespace WTF; | |
43 | |
44 namespace blink { | |
45 | |
46 // true if there is more to parse, after incrementing pos past whitespace. | |
47 // Note: Might return pos == str.length() | |
48 static inline bool skipWhiteSpace(const String& str, unsigned& pos, bool fromHtt
pEquivMeta) | |
49 { | |
50 unsigned len = str.length(); | |
51 | |
52 if (fromHttpEquivMeta) { | |
53 while (pos < len && str[pos] <= ' ') | |
54 ++pos; | |
55 } else { | |
56 while (pos < len && (str[pos] == '\t' || str[pos] == ' ')) | |
57 ++pos; | |
58 } | |
59 | |
60 return pos < len; | |
61 } | |
62 | |
63 // Returns true if the function can match the whole token (case insensitive) | |
64 // incrementing pos on match, otherwise leaving pos unchanged. | |
65 // Note: Might return pos == str.length() | |
66 static inline bool skipToken(const String& str, unsigned& pos, const char* token
) | |
67 { | |
68 unsigned len = str.length(); | |
69 unsigned current = pos; | |
70 | |
71 while (current < len && *token) { | |
72 if (toASCIILower(str[current]) != *token++) | |
73 return false; | |
74 ++current; | |
75 } | |
76 | |
77 if (*token) | |
78 return false; | |
79 | |
80 pos = current; | |
81 return true; | |
82 } | |
83 | |
84 // True if the expected equals sign is seen and there is more to follow. | |
85 static inline bool skipEquals(const String& str, unsigned &pos) | |
86 { | |
87 return skipWhiteSpace(str, pos, false) && str[pos++] == '=' && skipWhiteSpac
e(str, pos, false); | |
88 } | |
89 | |
90 // True if a value present, incrementing pos to next space or semicolon, if any. | |
91 // Note: might return pos == str.length(). | |
92 static inline bool skipValue(const String& str, unsigned& pos) | |
93 { | |
94 unsigned start = pos; | |
95 unsigned len = str.length(); | |
96 while (pos < len) { | |
97 if (str[pos] == ' ' || str[pos] == '\t' || str[pos] == ';') | |
98 break; | |
99 ++pos; | |
100 } | |
101 return pos != start; | |
102 } | |
103 | |
104 bool isValidHTTPHeaderValue(const String& name) | |
105 { | |
106 // FIXME: This should really match name against | |
107 // field-value in section 4.2 of RFC 2616. | |
108 | |
109 return name.containsOnlyLatin1() && !name.contains('\r') && !name.contains('
\n'); | |
110 } | |
111 | |
112 // See RFC 2616, Section 2.2. | |
113 bool isValidHTTPToken(const String& characters) | |
114 { | |
115 if (characters.isEmpty()) | |
116 return false; | |
117 for (unsigned i = 0; i < characters.length(); ++i) { | |
118 UChar c = characters[i]; | |
119 if (c <= 0x20 || c >= 0x7F | |
120 || c == '(' || c == ')' || c == '<' || c == '>' || c == '@' | |
121 || c == ',' || c == ';' || c == ':' || c == '\\' || c == '"' | |
122 || c == '/' || c == '[' || c == ']' || c == '?' || c == '=' | |
123 || c == '{' || c == '}') | |
124 return false; | |
125 } | |
126 return true; | |
127 } | |
128 | |
129 static const size_t maxInputSampleSize = 128; | |
130 static String trimInputSample(const char* p, size_t length) | |
131 { | |
132 if (length > maxInputSampleSize) | |
133 return String(p, maxInputSampleSize) + horizontalEllipsis; | |
134 return String(p, length); | |
135 } | |
136 | |
137 ContentDispositionType contentDispositionType(const String& contentDisposition) | |
138 { | |
139 if (contentDisposition.isEmpty()) | |
140 return ContentDispositionNone; | |
141 | |
142 Vector<String> parameters; | |
143 contentDisposition.split(';', parameters); | |
144 | |
145 if (parameters.isEmpty()) | |
146 return ContentDispositionNone; | |
147 | |
148 String dispositionType = parameters[0]; | |
149 dispositionType.stripWhiteSpace(); | |
150 | |
151 if (equalIgnoringCase(dispositionType, "inline")) | |
152 return ContentDispositionInline; | |
153 | |
154 // Some broken sites just send bogus headers like | |
155 // | |
156 // Content-Disposition: ; filename="file" | |
157 // Content-Disposition: filename="file" | |
158 // Content-Disposition: name="file" | |
159 // | |
160 // without a disposition token... screen those out. | |
161 if (!isValidHTTPToken(dispositionType)) | |
162 return ContentDispositionNone; | |
163 | |
164 // We have a content-disposition of "attachment" or unknown. | |
165 // RFC 2183, section 2.8 says that an unknown disposition | |
166 // value should be treated as "attachment" | |
167 return ContentDispositionAttachment; | |
168 } | |
169 | |
170 double parseDate(const String& value) | |
171 { | |
172 return parseDateFromNullTerminatedCharacters(value.utf8().data()); | |
173 } | |
174 | |
175 // FIXME: This function doesn't comply with RFC 6266. | |
176 // For example, this function doesn't handle the interaction between " and ; | |
177 // that arises from quoted-string, nor does this function properly unquote | |
178 // attribute values. Further this function appears to process parameter names | |
179 // in a case-sensitive manner. (There are likely other bugs as well.) | |
180 String filenameFromHTTPContentDisposition(const String& value) | |
181 { | |
182 Vector<String> keyValuePairs; | |
183 value.split(';', keyValuePairs); | |
184 | |
185 unsigned length = keyValuePairs.size(); | |
186 for (unsigned i = 0; i < length; i++) { | |
187 size_t valueStartPos = keyValuePairs[i].find('='); | |
188 if (valueStartPos == kNotFound) | |
189 continue; | |
190 | |
191 String key = keyValuePairs[i].left(valueStartPos).stripWhiteSpace(); | |
192 | |
193 if (key.isEmpty() || key != "filename") | |
194 continue; | |
195 | |
196 String value = keyValuePairs[i].substring(valueStartPos + 1).stripWhiteS
pace(); | |
197 | |
198 // Remove quotes if there are any | |
199 if (value[0] == '\"') | |
200 value = value.substring(1, value.length() - 2); | |
201 | |
202 return value; | |
203 } | |
204 | |
205 return String(); | |
206 } | |
207 | |
208 AtomicString extractMIMETypeFromMediaType(const AtomicString& mediaType) | |
209 { | |
210 StringBuilder mimeType; | |
211 unsigned length = mediaType.length(); | |
212 mimeType.reserveCapacity(length); | |
213 for (unsigned i = 0; i < length; i++) { | |
214 UChar c = mediaType[i]; | |
215 | |
216 if (c == ';') | |
217 break; | |
218 | |
219 // While RFC 2616 does not allow it, other browsers allow multiple value
s in the HTTP media | |
220 // type header field, Content-Type. In such cases, the media type string
passed here may contain | |
221 // the multiple values separated by commas. For now, this code ignores t
ext after the first comma, | |
222 // which prevents it from simply failing to parse such types altogether.
Later for better | |
223 // compatibility we could consider using the first or last valid MIME ty
pe instead. | |
224 // See https://bugs.webkit.org/show_bug.cgi?id=25352 for more discussion
. | |
225 if (c == ',') | |
226 break; | |
227 | |
228 // FIXME: The following is not correct. RFC 2616 allows linear white spa
ce before and | |
229 // after the MIME type, but not within the MIME type itself. And linear
white space | |
230 // includes only a few specific ASCII characters; a small subset of isSp
aceOrNewline. | |
231 // See https://bugs.webkit.org/show_bug.cgi?id=8644 for a bug tracking p
art of this. | |
232 if (isSpaceOrNewline(c)) | |
233 continue; | |
234 | |
235 mimeType.append(c); | |
236 } | |
237 | |
238 if (mimeType.length() == length) | |
239 return mediaType; | |
240 return mimeType.toAtomicString(); | |
241 } | |
242 | |
243 String extractCharsetFromMediaType(const String& mediaType) | |
244 { | |
245 unsigned pos, len; | |
246 findCharsetInMediaType(mediaType, pos, len); | |
247 return mediaType.substring(pos, len); | |
248 } | |
249 | |
250 void findCharsetInMediaType(const String& mediaType, unsigned& charsetPos, unsig
ned& charsetLen, unsigned start) | |
251 { | |
252 charsetPos = start; | |
253 charsetLen = 0; | |
254 | |
255 size_t pos = start; | |
256 unsigned length = mediaType.length(); | |
257 | |
258 while (pos < length) { | |
259 pos = mediaType.find("charset", pos, false); | |
260 if (pos == kNotFound || !pos) { | |
261 charsetLen = 0; | |
262 return; | |
263 } | |
264 | |
265 // is what we found a beginning of a word? | |
266 if (mediaType[pos-1] > ' ' && mediaType[pos-1] != ';') { | |
267 pos += 7; | |
268 continue; | |
269 } | |
270 | |
271 pos += 7; | |
272 | |
273 // skip whitespace | |
274 while (pos != length && mediaType[pos] <= ' ') | |
275 ++pos; | |
276 | |
277 if (mediaType[pos++] != '=') // this "charset" substring wasn't a parame
ter name, but there may be others | |
278 continue; | |
279 | |
280 while (pos != length && (mediaType[pos] <= ' ' || mediaType[pos] == '"'
|| mediaType[pos] == '\'')) | |
281 ++pos; | |
282 | |
283 // we don't handle spaces within quoted parameter values, because charse
t names cannot have any | |
284 unsigned endpos = pos; | |
285 while (pos != length && mediaType[endpos] > ' ' && mediaType[endpos] !=
'"' && mediaType[endpos] != '\'' && mediaType[endpos] != ';') | |
286 ++endpos; | |
287 | |
288 charsetPos = pos; | |
289 charsetLen = endpos - pos; | |
290 return; | |
291 } | |
292 } | |
293 | |
294 ReflectedXSSDisposition parseXSSProtectionHeader(const String& header, String& f
ailureReason, unsigned& failurePosition, String& reportURL) | |
295 { | |
296 DEFINE_STATIC_LOCAL(String, failureReasonInvalidToggle, ("expected 0 or 1"))
; | |
297 DEFINE_STATIC_LOCAL(String, failureReasonInvalidSeparator, ("expected semico
lon")); | |
298 DEFINE_STATIC_LOCAL(String, failureReasonInvalidEquals, ("expected equals si
gn")); | |
299 DEFINE_STATIC_LOCAL(String, failureReasonInvalidMode, ("invalid mode directi
ve")); | |
300 DEFINE_STATIC_LOCAL(String, failureReasonInvalidReport, ("invalid report dir
ective")); | |
301 DEFINE_STATIC_LOCAL(String, failureReasonDuplicateMode, ("duplicate mode dir
ective")); | |
302 DEFINE_STATIC_LOCAL(String, failureReasonDuplicateReport, ("duplicate report
directive")); | |
303 DEFINE_STATIC_LOCAL(String, failureReasonInvalidDirective, ("unrecognized di
rective")); | |
304 | |
305 unsigned pos = 0; | |
306 | |
307 if (!skipWhiteSpace(header, pos, false)) | |
308 return ReflectedXSSUnset; | |
309 | |
310 if (header[pos] == '0') | |
311 return AllowReflectedXSS; | |
312 | |
313 if (header[pos++] != '1') { | |
314 failureReason = failureReasonInvalidToggle; | |
315 return ReflectedXSSInvalid; | |
316 } | |
317 | |
318 ReflectedXSSDisposition result = FilterReflectedXSS; | |
319 bool modeDirectiveSeen = false; | |
320 bool reportDirectiveSeen = false; | |
321 | |
322 while (1) { | |
323 // At end of previous directive: consume whitespace, semicolon, and whit
espace. | |
324 if (!skipWhiteSpace(header, pos, false)) | |
325 return result; | |
326 | |
327 if (header[pos++] != ';') { | |
328 failureReason = failureReasonInvalidSeparator; | |
329 failurePosition = pos; | |
330 return ReflectedXSSInvalid; | |
331 } | |
332 | |
333 if (!skipWhiteSpace(header, pos, false)) | |
334 return result; | |
335 | |
336 // At start of next directive. | |
337 if (skipToken(header, pos, "mode")) { | |
338 if (modeDirectiveSeen) { | |
339 failureReason = failureReasonDuplicateMode; | |
340 failurePosition = pos; | |
341 return ReflectedXSSInvalid; | |
342 } | |
343 modeDirectiveSeen = true; | |
344 if (!skipEquals(header, pos)) { | |
345 failureReason = failureReasonInvalidEquals; | |
346 failurePosition = pos; | |
347 return ReflectedXSSInvalid; | |
348 } | |
349 if (!skipToken(header, pos, "block")) { | |
350 failureReason = failureReasonInvalidMode; | |
351 failurePosition = pos; | |
352 return ReflectedXSSInvalid; | |
353 } | |
354 result = BlockReflectedXSS; | |
355 } else if (skipToken(header, pos, "report")) { | |
356 if (reportDirectiveSeen) { | |
357 failureReason = failureReasonDuplicateReport; | |
358 failurePosition = pos; | |
359 return ReflectedXSSInvalid; | |
360 } | |
361 reportDirectiveSeen = true; | |
362 if (!skipEquals(header, pos)) { | |
363 failureReason = failureReasonInvalidEquals; | |
364 failurePosition = pos; | |
365 return ReflectedXSSInvalid; | |
366 } | |
367 size_t startPos = pos; | |
368 if (!skipValue(header, pos)) { | |
369 failureReason = failureReasonInvalidReport; | |
370 failurePosition = pos; | |
371 return ReflectedXSSInvalid; | |
372 } | |
373 reportURL = header.substring(startPos, pos - startPos); | |
374 failurePosition = startPos; // If later semantic check deems unaccep
table. | |
375 } else { | |
376 failureReason = failureReasonInvalidDirective; | |
377 failurePosition = pos; | |
378 return ReflectedXSSInvalid; | |
379 } | |
380 } | |
381 } | |
382 | |
383 ContentTypeOptionsDisposition parseContentTypeOptionsHeader(const String& header
) | |
384 { | |
385 if (header.stripWhiteSpace().lower() == "nosniff") | |
386 return ContentTypeOptionsNosniff; | |
387 return ContentTypeOptionsNone; | |
388 } | |
389 | |
390 String extractReasonPhraseFromHTTPStatusLine(const String& statusLine) | |
391 { | |
392 size_t spacePos = statusLine.find(' '); | |
393 // Remove status code from the status line. | |
394 spacePos = statusLine.find(' ', spacePos + 1); | |
395 return statusLine.substring(spacePos + 1); | |
396 } | |
397 | |
398 bool parseRange(const String& range, long long& rangeOffset, long long& rangeEnd
, long long& rangeSuffixLength) | |
399 { | |
400 // The format of "Range" header is defined in RFC 2616 Section 14.35.1. | |
401 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1 | |
402 // We don't support multiple range requests. | |
403 | |
404 rangeOffset = rangeEnd = rangeSuffixLength = -1; | |
405 | |
406 // The "bytes" unit identifier should be present. | |
407 static const char bytesStart[] = "bytes="; | |
408 if (!range.startsWith(bytesStart, false)) | |
409 return false; | |
410 String byteRange = range.substring(sizeof(bytesStart) - 1); | |
411 | |
412 // The '-' character needs to be present. | |
413 int index = byteRange.find('-'); | |
414 if (index == -1) | |
415 return false; | |
416 | |
417 // If the '-' character is at the beginning, the suffix length, which specif
ies the last N bytes, is provided. | |
418 // Example: | |
419 // -500 | |
420 if (!index) { | |
421 String suffixLengthString = byteRange.substring(index + 1).stripWhiteSpa
ce(); | |
422 bool ok; | |
423 long long value = suffixLengthString.toInt64Strict(&ok); | |
424 if (ok) | |
425 rangeSuffixLength = value; | |
426 return true; | |
427 } | |
428 | |
429 // Otherwise, the first-byte-position and the last-byte-position are provied
. | |
430 // Examples: | |
431 // 0-499 | |
432 // 500- | |
433 String firstBytePosStr = byteRange.left(index).stripWhiteSpace(); | |
434 bool ok; | |
435 long long firstBytePos = firstBytePosStr.toInt64Strict(&ok); | |
436 if (!ok) | |
437 return false; | |
438 | |
439 String lastBytePosStr = byteRange.substring(index + 1).stripWhiteSpace(); | |
440 long long lastBytePos = -1; | |
441 if (!lastBytePosStr.isEmpty()) { | |
442 lastBytePos = lastBytePosStr.toInt64Strict(&ok); | |
443 if (!ok) | |
444 return false; | |
445 } | |
446 | |
447 if (firstBytePos < 0 || !(lastBytePos == -1 || lastBytePos >= firstBytePos)) | |
448 return false; | |
449 | |
450 rangeOffset = firstBytePos; | |
451 rangeEnd = lastBytePos; | |
452 return true; | |
453 } | |
454 | |
455 // HTTP/1.1 - RFC 2616 | |
456 // http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1 | |
457 // Request-Line = Method SP Request-URI SP HTTP-Version CRLF | |
458 size_t parseHTTPRequestLine(const char* data, size_t length, String& failureReas
on, String& method, String& url, HTTPVersion& httpVersion) | |
459 { | |
460 method = String(); | |
461 url = String(); | |
462 httpVersion = Unknown; | |
463 | |
464 const char* space1 = 0; | |
465 const char* space2 = 0; | |
466 const char* p; | |
467 size_t consumedLength; | |
468 | |
469 for (p = data, consumedLength = 0; consumedLength < length; p++, consumedLen
gth++) { | |
470 if (*p == ' ') { | |
471 if (!space1) | |
472 space1 = p; | |
473 else if (!space2) | |
474 space2 = p; | |
475 } else if (*p == '\n') { | |
476 break; | |
477 } | |
478 } | |
479 | |
480 // Haven't finished header line. | |
481 if (consumedLength == length) { | |
482 failureReason = "Incomplete Request Line"; | |
483 return 0; | |
484 } | |
485 | |
486 // RequestLine does not contain 3 parts. | |
487 if (!space1 || !space2) { | |
488 failureReason = "Request Line does not appear to contain: <Method> <Url>
<HTTPVersion>."; | |
489 return 0; | |
490 } | |
491 | |
492 // The line must end with "\r\n". | |
493 const char* end = p + 1; | |
494 if (*(end - 2) != '\r') { | |
495 failureReason = "Request line does not end with CRLF"; | |
496 return 0; | |
497 } | |
498 | |
499 // Request Method. | |
500 method = String(data, space1 - data); // For length subtract 1 for space, bu
t add 1 for data being the first character. | |
501 | |
502 // Request URI. | |
503 url = String(space1 + 1, space2 - space1 - 1); // For length subtract 1 for
space. | |
504 | |
505 // HTTP Version. | |
506 String httpVersionString(space2 + 1, end - space2 - 3); // For length subtra
ct 1 for space, and 2 for "\r\n". | |
507 if (httpVersionString.length() != 8 || !httpVersionString.startsWith("HTTP/1
.")) | |
508 httpVersion = Unknown; | |
509 else if (httpVersionString[7] == '0') | |
510 httpVersion = HTTP_1_0; | |
511 else if (httpVersionString[7] == '1') | |
512 httpVersion = HTTP_1_1; | |
513 else | |
514 httpVersion = Unknown; | |
515 | |
516 return end - data; | |
517 } | |
518 | |
519 static bool parseHTTPHeaderName(const char* s, size_t start, size_t size, String
& failureReason, size_t* position, AtomicString* name) | |
520 { | |
521 size_t nameBegin = start; | |
522 for (size_t i = start; i < size; ++i) { | |
523 switch (s[i]) { | |
524 case '\r': | |
525 failureReason = "Unexpected CR in name at " + trimInputSample(&s[nam
eBegin], i - nameBegin); | |
526 return false; | |
527 case '\n': | |
528 failureReason = "Unexpected LF in name at " + trimInputSample(&s[nam
eBegin], i - nameBegin); | |
529 return false; | |
530 case ':': | |
531 if (i == nameBegin) { | |
532 failureReason = "Header name is missing"; | |
533 return false; | |
534 } | |
535 *name = AtomicString::fromUTF8(&s[nameBegin], i - nameBegin); | |
536 if (name->isNull()) { | |
537 failureReason = "Invalid UTF-8 sequence in header name"; | |
538 return false; | |
539 } | |
540 *position = i; | |
541 return true; | |
542 default: | |
543 break; | |
544 } | |
545 } | |
546 failureReason = "Unterminated header name"; | |
547 return false; | |
548 } | |
549 | |
550 static bool parseHTTPHeaderValue(const char* s, size_t start, size_t size, Strin
g& failureReason, size_t* position, AtomicString* value) | |
551 { | |
552 size_t i = start; | |
553 for (; i < size && s[i] == ' '; ++i) { | |
554 } | |
555 size_t valueBegin = i; | |
556 | |
557 for (; i < size && s[i] != '\r'; ++i) { | |
558 if (s[i] == '\n') { | |
559 failureReason = "Unexpected LF in value at " + trimInputSample(&s[va
lueBegin], i - valueBegin); | |
560 return false; | |
561 } | |
562 } | |
563 if (i == size) { | |
564 failureReason = "Unterminated header value"; | |
565 return false; | |
566 } | |
567 | |
568 ASSERT(i < size && s[i] == '\r'); | |
569 if (i + 1 >= size || s[i + 1] != '\n') { | |
570 failureReason = "LF doesn't follow CR after value at " + trimInputSample
(&s[i + 1], size - i - 1); | |
571 return false; | |
572 } | |
573 | |
574 *value = AtomicString::fromUTF8(&s[valueBegin], i - valueBegin); | |
575 if (i != valueBegin && value->isNull()) { | |
576 failureReason = "Invalid UTF-8 sequence in header value"; | |
577 return false; | |
578 } | |
579 | |
580 // 2 for strlen("\r\n") | |
581 *position = i + 2; | |
582 return true; | |
583 } | |
584 | |
585 // Note that the header is already parsed and re-formatted in chromium side. | |
586 // We assume that the input is more restricted than RFC2616. | |
587 size_t parseHTTPHeader(const char* s, size_t size, String& failureReason, Atomic
String& name, AtomicString& value) | |
588 { | |
589 name = nullAtom; | |
590 value = nullAtom; | |
591 if (size >= 1 && s[0] == '\r') { | |
592 if (size >= 2 && s[1] == '\n') { | |
593 // Skip an empty line. | |
594 return 2; | |
595 } | |
596 failureReason = "LF doesn't follow CR at " + trimInputSample(0, size); | |
597 return 0; | |
598 } | |
599 size_t current = 0; | |
600 if (!parseHTTPHeaderName(s, current, size, failureReason, ¤t, &name))
{ | |
601 return 0; | |
602 } | |
603 ASSERT(s[current] == ':'); | |
604 ++current; | |
605 | |
606 if (!parseHTTPHeaderValue(s, current, size, failureReason, ¤t, &value)
) { | |
607 return 0; | |
608 } | |
609 | |
610 return current; | |
611 } | |
612 | |
613 size_t parseHTTPRequestBody(const char* data, size_t length, Vector<unsigned cha
r>& body) | |
614 { | |
615 body.clear(); | |
616 body.append(data, length); | |
617 | |
618 return length; | |
619 } | |
620 | |
621 static bool isCacheHeaderSeparator(UChar c) | |
622 { | |
623 // See RFC 2616, Section 2.2 | |
624 switch (c) { | |
625 case '(': | |
626 case ')': | |
627 case '<': | |
628 case '>': | |
629 case '@': | |
630 case ',': | |
631 case ';': | |
632 case ':': | |
633 case '\\': | |
634 case '"': | |
635 case '/': | |
636 case '[': | |
637 case ']': | |
638 case '?': | |
639 case '=': | |
640 case '{': | |
641 case '}': | |
642 case ' ': | |
643 case '\t': | |
644 return true; | |
645 default: | |
646 return false; | |
647 } | |
648 } | |
649 | |
650 static bool isControlCharacter(UChar c) | |
651 { | |
652 return c < ' ' || c == 127; | |
653 } | |
654 | |
655 static inline String trimToNextSeparator(const String& str) | |
656 { | |
657 return str.substring(0, str.find(isCacheHeaderSeparator)); | |
658 } | |
659 | |
660 static void parseCacheHeader(const String& header, Vector<pair<String, String> >
& result) | |
661 { | |
662 const String safeHeader = header.removeCharacters(isControlCharacter); | |
663 unsigned max = safeHeader.length(); | |
664 for (unsigned pos = 0; pos < max; /* pos incremented in loop */) { | |
665 size_t nextCommaPosition = safeHeader.find(',', pos); | |
666 size_t nextEqualSignPosition = safeHeader.find('=', pos); | |
667 if (nextEqualSignPosition != kNotFound && (nextEqualSignPosition < nextC
ommaPosition || nextCommaPosition == kNotFound)) { | |
668 // Get directive name, parse right hand side of equal sign, then add
to map | |
669 String directive = trimToNextSeparator(safeHeader.substring(pos, nex
tEqualSignPosition - pos).stripWhiteSpace()); | |
670 pos += nextEqualSignPosition - pos + 1; | |
671 | |
672 String value = safeHeader.substring(pos, max - pos).stripWhiteSpace(
); | |
673 if (value[0] == '"') { | |
674 // The value is a quoted string | |
675 size_t nextDoubleQuotePosition = value.find('"', 1); | |
676 if (nextDoubleQuotePosition != kNotFound) { | |
677 // Store the value as a quoted string without quotes | |
678 result.append(pair<String, String>(directive, value.substrin
g(1, nextDoubleQuotePosition - 1).stripWhiteSpace())); | |
679 pos += (safeHeader.find('"', pos) - pos) + nextDoubleQuotePo
sition + 1; | |
680 // Move past next comma, if there is one | |
681 size_t nextCommaPosition2 = safeHeader.find(',', pos); | |
682 if (nextCommaPosition2 != kNotFound) | |
683 pos += nextCommaPosition2 - pos + 1; | |
684 else | |
685 return; // Parse error if there is anything left with no
comma | |
686 } else { | |
687 // Parse error; just use the rest as the value | |
688 result.append(pair<String, String>(directive, trimToNextSepa
rator(value.substring(1, value.length() - 1).stripWhiteSpace()))); | |
689 return; | |
690 } | |
691 } else { | |
692 // The value is a token until the next comma | |
693 size_t nextCommaPosition2 = value.find(','); | |
694 if (nextCommaPosition2 != kNotFound) { | |
695 // The value is delimited by the next comma | |
696 result.append(pair<String, String>(directive, trimToNextSepa
rator(value.substring(0, nextCommaPosition2).stripWhiteSpace()))); | |
697 pos += (safeHeader.find(',', pos) - pos) + 1; | |
698 } else { | |
699 // The rest is the value; no change to value needed | |
700 result.append(pair<String, String>(directive, trimToNextSepa
rator(value))); | |
701 return; | |
702 } | |
703 } | |
704 } else if (nextCommaPosition != kNotFound && (nextCommaPosition < nextEq
ualSignPosition || nextEqualSignPosition == kNotFound)) { | |
705 // Add directive to map with empty string as value | |
706 result.append(pair<String, String>(trimToNextSeparator(safeHeader.su
bstring(pos, nextCommaPosition - pos).stripWhiteSpace()), "")); | |
707 pos += nextCommaPosition - pos + 1; | |
708 } else { | |
709 // Add last directive to map with empty string as value | |
710 result.append(pair<String, String>(trimToNextSeparator(safeHeader.su
bstring(pos, max - pos).stripWhiteSpace()), "")); | |
711 return; | |
712 } | |
713 } | |
714 } | |
715 | |
716 CacheControlHeader parseCacheControlDirectives(const AtomicString& cacheControlV
alue, const AtomicString& pragmaValue) | |
717 { | |
718 CacheControlHeader cacheControlHeader; | |
719 cacheControlHeader.parsed = true; | |
720 cacheControlHeader.maxAge = std::numeric_limits<double>::quiet_NaN(); | |
721 | |
722 DEFINE_STATIC_LOCAL(const AtomicString, noCacheDirective, ("no-cache", Atomi
cString::ConstructFromLiteral)); | |
723 DEFINE_STATIC_LOCAL(const AtomicString, noStoreDirective, ("no-store", Atomi
cString::ConstructFromLiteral)); | |
724 DEFINE_STATIC_LOCAL(const AtomicString, mustRevalidateDirective, ("must-reva
lidate", AtomicString::ConstructFromLiteral)); | |
725 DEFINE_STATIC_LOCAL(const AtomicString, maxAgeDirective, ("max-age", AtomicS
tring::ConstructFromLiteral)); | |
726 | |
727 if (!cacheControlValue.isEmpty()) { | |
728 Vector<pair<String, String> > directives; | |
729 parseCacheHeader(cacheControlValue, directives); | |
730 | |
731 size_t directivesSize = directives.size(); | |
732 for (size_t i = 0; i < directivesSize; ++i) { | |
733 // RFC2616 14.9.1: A no-cache directive with a value is only meaning
ful for proxy caches. | |
734 // It should be ignored by a browser level cache. | |
735 if (equalIgnoringCase(directives[i].first, noCacheDirective) && dire
ctives[i].second.isEmpty()) { | |
736 cacheControlHeader.containsNoCache = true; | |
737 } else if (equalIgnoringCase(directives[i].first, noStoreDirective))
{ | |
738 cacheControlHeader.containsNoStore = true; | |
739 } else if (equalIgnoringCase(directives[i].first, mustRevalidateDire
ctive)) { | |
740 cacheControlHeader.containsMustRevalidate = true; | |
741 } else if (equalIgnoringCase(directives[i].first, maxAgeDirective))
{ | |
742 if (!std::isnan(cacheControlHeader.maxAge)) { | |
743 // First max-age directive wins if there are multiple ones. | |
744 continue; | |
745 } | |
746 bool ok; | |
747 double maxAge = directives[i].second.toDouble(&ok); | |
748 if (ok) | |
749 cacheControlHeader.maxAge = maxAge; | |
750 } | |
751 } | |
752 } | |
753 | |
754 if (!cacheControlHeader.containsNoCache) { | |
755 // Handle Pragma: no-cache | |
756 // This is deprecated and equivalent to Cache-control: no-cache | |
757 // Don't bother tokenizing the value, it is not important | |
758 cacheControlHeader.containsNoCache = pragmaValue.lower().contains(noCach
eDirective); | |
759 } | |
760 return cacheControlHeader; | |
761 } | |
762 | |
763 } | |
OLD | NEW |