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 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 "config.h"
34#include "HTTPParsers.h"
35
36#include "ContentSecurityPolicy.h"
37#include <wtf/DateMath.h>
38#include <wtf/text/CString.h>
39#include <wtf/text/StringBuilder.h>
40#include <wtf/text/WTFString.h>
41#include <wtf/unicode/CharacterNames.h>
42
43using namespace WTF;
44
45namespace WebCore {
46
47// true if there is more to parse, after incrementing pos past whitespace.
48// Note: Might return pos == str.length()
49static inline bool skipWhiteSpace(const String& str, unsigned& pos, bool fromHttpEquivMeta)
50{
51    unsigned len = str.length();
52
53    if (fromHttpEquivMeta) {
54        while (pos < len && str[pos] <= ' ')
55            ++pos;
56    } else {
57        while (pos < len && (str[pos] == '\t' || str[pos] == ' '))
58            ++pos;
59    }
60
61    return pos < len;
62}
63
64// Returns true if the function can match the whole token (case insensitive)
65// incrementing pos on match, otherwise leaving pos unchanged.
66// Note: Might return pos == str.length()
67static inline bool skipToken(const String& str, unsigned& pos, const char* token)
68{
69    unsigned len = str.length();
70    unsigned current = pos;
71
72    while (current < len && *token) {
73        if (toASCIILower(str[current]) != *token++)
74            return false;
75        ++current;
76    }
77
78    if (*token)
79        return false;
80
81    pos = current;
82    return true;
83}
84
85// True if the expected equals sign is seen and there is more to follow.
86static inline bool skipEquals(const String& str, unsigned &pos)
87{
88    return skipWhiteSpace(str, pos, false) && str[pos++] == '=' && skipWhiteSpace(str, pos, false);
89}
90
91// True if a value present, incrementing pos to next space or semicolon, if any.
92// Note: might return pos == str.length().
93static inline bool skipValue(const String& str, unsigned& pos)
94{
95    unsigned start = pos;
96    unsigned len = str.length();
97    while (pos < len) {
98        if (str[pos] == ' ' || str[pos] == '\t' || str[pos] == ';')
99            break;
100        ++pos;
101    }
102    return pos != start;
103}
104
105bool isValidHTTPHeaderValue(const String& name)
106{
107    // FIXME: This should really match name against
108    // field-value in section 4.2 of RFC 2616.
109
110    return !name.contains('\r') && !name.contains('\n');
111}
112
113// See RFC 2616, Section 2.2.
114bool isValidHTTPToken(const String& characters)
115{
116    if (characters.isEmpty())
117        return false;
118    for (unsigned i = 0; i < characters.length(); ++i) {
119        UChar c = characters[i];
120        if (c <= 0x20 || c >= 0x7F
121            || c == '(' || c == ')' || c == '<' || c == '>' || c == '@'
122            || c == ',' || c == ';' || c == ':' || c == '\\' || c == '"'
123            || c == '/' || c == '[' || c == ']' || c == '?' || c == '='
124            || c == '{' || c == '}')
125        return false;
126    }
127    return true;
128}
129
130static const size_t maxInputSampleSize = 128;
131static String trimInputSample(const char* p, size_t length)
132{
133    String s = String(p, std::min<size_t>(length, maxInputSampleSize));
134    if (length > maxInputSampleSize)
135        s.append(horizontalEllipsis);
136    return s;
137}
138
139ContentDispositionType contentDispositionType(const String& contentDisposition)
140{
141    if (contentDisposition.isEmpty())
142        return ContentDispositionNone;
143
144    Vector<String> parameters;
145    contentDisposition.split(';', parameters);
146
147    String dispositionType = parameters[0];
148    dispositionType.stripWhiteSpace();
149
150    if (equalIgnoringCase(dispositionType, "inline"))
151        return ContentDispositionInline;
152
153    // Some broken sites just send bogus headers like
154    //
155    //   Content-Disposition: ; filename="file"
156    //   Content-Disposition: filename="file"
157    //   Content-Disposition: name="file"
158    //
159    // without a disposition token... screen those out.
160    if (!isValidHTTPToken(dispositionType))
161        return ContentDispositionNone;
162
163    // We have a content-disposition of "attachment" or unknown.
164    // RFC 2183, section 2.8 says that an unknown disposition
165    // value should be treated as "attachment"
166    return ContentDispositionAttachment;
167}
168
169bool parseHTTPRefresh(const String& refresh, bool fromHttpEquivMeta, double& delay, String& url)
170{
171    unsigned len = refresh.length();
172    unsigned pos = 0;
173
174    if (!skipWhiteSpace(refresh, pos, fromHttpEquivMeta))
175        return false;
176
177    while (pos != len && refresh[pos] != ',' && refresh[pos] != ';')
178        ++pos;
179
180    if (pos == len) { // no URL
181        url = String();
182        bool ok;
183        delay = refresh.stripWhiteSpace().toDouble(&ok);
184        return ok;
185    } else {
186        bool ok;
187        delay = refresh.left(pos).stripWhiteSpace().toDouble(&ok);
188        if (!ok)
189            return false;
190
191        ++pos;
192        skipWhiteSpace(refresh, pos, fromHttpEquivMeta);
193        unsigned urlStartPos = pos;
194        if (refresh.find("url", urlStartPos, false) == urlStartPos) {
195            urlStartPos += 3;
196            skipWhiteSpace(refresh, urlStartPos, fromHttpEquivMeta);
197            if (refresh[urlStartPos] == '=') {
198                ++urlStartPos;
199                skipWhiteSpace(refresh, urlStartPos, fromHttpEquivMeta);
200            } else
201                urlStartPos = pos;  // e.g. "Refresh: 0; url.html"
202        }
203
204        unsigned urlEndPos = len;
205
206        if (refresh[urlStartPos] == '"' || refresh[urlStartPos] == '\'') {
207            UChar quotationMark = refresh[urlStartPos];
208            urlStartPos++;
209            while (urlEndPos > urlStartPos) {
210                urlEndPos--;
211                if (refresh[urlEndPos] == quotationMark)
212                    break;
213            }
214
215            // https://bugs.webkit.org/show_bug.cgi?id=27868
216            // Sometimes there is no closing quote for the end of the URL even though there was an opening quote.
217            // If we looped over the entire alleged URL string back to the opening quote, just go ahead and use everything
218            // after the opening quote instead.
219            if (urlEndPos == urlStartPos)
220                urlEndPos = len;
221        }
222
223        url = refresh.substring(urlStartPos, urlEndPos - urlStartPos).stripWhiteSpace();
224        return true;
225    }
226}
227
228double parseDate(const String& value)
229{
230    return parseDateFromNullTerminatedCharacters(value.utf8().data());
231}
232
233// FIXME: This function doesn't comply with RFC 6266.
234// For example, this function doesn't handle the interaction between " and ;
235// that arises from quoted-string, nor does this function properly unquote
236// attribute values. Further this function appears to process parameter names
237// in a case-sensitive manner. (There are likely other bugs as well.)
238String filenameFromHTTPContentDisposition(const String& value)
239{
240    Vector<String> keyValuePairs;
241    value.split(';', keyValuePairs);
242
243    unsigned length = keyValuePairs.size();
244    for (unsigned i = 0; i < length; i++) {
245        size_t valueStartPos = keyValuePairs[i].find('=');
246        if (valueStartPos == notFound)
247            continue;
248
249        String key = keyValuePairs[i].left(valueStartPos).stripWhiteSpace();
250
251        if (key.isEmpty() || key != "filename")
252            continue;
253
254        String value = keyValuePairs[i].substring(valueStartPos + 1).stripWhiteSpace();
255
256        // Remove quotes if there are any
257        if (value[0] == '\"')
258            value = value.substring(1, value.length() - 2);
259
260        return value;
261    }
262
263    return String();
264}
265
266String extractMIMETypeFromMediaType(const String& mediaType)
267{
268    StringBuilder mimeType;
269    unsigned length = mediaType.length();
270    mimeType.reserveCapacity(length);
271    for (unsigned i = 0; i < length; i++) {
272        UChar c = mediaType[i];
273
274        if (c == ';')
275            break;
276
277        // While RFC 2616 does not allow it, other browsers allow multiple values in the HTTP media
278        // type header field, Content-Type. In such cases, the media type string passed here may contain
279        // the multiple values separated by commas. For now, this code ignores text after the first comma,
280        // which prevents it from simply failing to parse such types altogether. Later for better
281        // compatibility we could consider using the first or last valid MIME type instead.
282        // See https://bugs.webkit.org/show_bug.cgi?id=25352 for more discussion.
283        if (c == ',')
284            break;
285
286        // FIXME: The following is not correct. RFC 2616 allows linear white space before and
287        // after the MIME type, but not within the MIME type itself. And linear white space
288        // includes only a few specific ASCII characters; a small subset of isSpaceOrNewline.
289        // See https://bugs.webkit.org/show_bug.cgi?id=8644 for a bug tracking part of this.
290        if (isSpaceOrNewline(c))
291            continue;
292
293        mimeType.append(c);
294    }
295
296    if (mimeType.length() == length)
297        return mediaType;
298    return mimeType.toString();
299}
300
301String extractCharsetFromMediaType(const String& mediaType)
302{
303    unsigned int pos, len;
304    findCharsetInMediaType(mediaType, pos, len);
305    return mediaType.substring(pos, len);
306}
307
308void findCharsetInMediaType(const String& mediaType, unsigned int& charsetPos, unsigned int& charsetLen, unsigned int start)
309{
310    charsetPos = start;
311    charsetLen = 0;
312
313    size_t pos = start;
314    unsigned length = mediaType.length();
315
316    while (pos < length) {
317        pos = mediaType.find("charset", pos, false);
318        if (pos == notFound || pos == 0) {
319            charsetLen = 0;
320            return;
321        }
322
323        // is what we found a beginning of a word?
324        if (mediaType[pos-1] > ' ' && mediaType[pos-1] != ';') {
325            pos += 7;
326            continue;
327        }
328
329        pos += 7;
330
331        // skip whitespace
332        while (pos != length && mediaType[pos] <= ' ')
333            ++pos;
334
335        if (mediaType[pos++] != '=') // this "charset" substring wasn't a parameter name, but there may be others
336            continue;
337
338        while (pos != length && (mediaType[pos] <= ' ' || mediaType[pos] == '"' || mediaType[pos] == '\''))
339            ++pos;
340
341        // we don't handle spaces within quoted parameter values, because charset names cannot have any
342        unsigned endpos = pos;
343        while (pos != length && mediaType[endpos] > ' ' && mediaType[endpos] != '"' && mediaType[endpos] != '\'' && mediaType[endpos] != ';')
344            ++endpos;
345
346        charsetPos = pos;
347        charsetLen = endpos - pos;
348        return;
349    }
350}
351
352ContentSecurityPolicy::ReflectedXSSDisposition parseXSSProtectionHeader(const String& header, String& failureReason, unsigned& failurePosition, String& reportURL)
353{
354    DEPRECATED_DEFINE_STATIC_LOCAL(String, failureReasonInvalidToggle, (ASCIILiteral("expected 0 or 1")));
355    DEPRECATED_DEFINE_STATIC_LOCAL(String, failureReasonInvalidSeparator, (ASCIILiteral("expected semicolon")));
356    DEPRECATED_DEFINE_STATIC_LOCAL(String, failureReasonInvalidEquals, (ASCIILiteral("expected equals sign")));
357    DEPRECATED_DEFINE_STATIC_LOCAL(String, failureReasonInvalidMode, (ASCIILiteral("invalid mode directive")));
358    DEPRECATED_DEFINE_STATIC_LOCAL(String, failureReasonInvalidReport, (ASCIILiteral("invalid report directive")));
359    DEPRECATED_DEFINE_STATIC_LOCAL(String, failureReasonDuplicateMode, (ASCIILiteral("duplicate mode directive")));
360    DEPRECATED_DEFINE_STATIC_LOCAL(String, failureReasonDuplicateReport, (ASCIILiteral("duplicate report directive")));
361    DEPRECATED_DEFINE_STATIC_LOCAL(String, failureReasonInvalidDirective, (ASCIILiteral("unrecognized directive")));
362
363    unsigned pos = 0;
364
365    if (!skipWhiteSpace(header, pos, false))
366        return ContentSecurityPolicy::ReflectedXSSUnset;
367
368    if (header[pos] == '0')
369        return ContentSecurityPolicy::AllowReflectedXSS;
370
371    if (header[pos++] != '1') {
372        failureReason = failureReasonInvalidToggle;
373        return ContentSecurityPolicy::ReflectedXSSInvalid;
374    }
375
376    ContentSecurityPolicy::ReflectedXSSDisposition result = ContentSecurityPolicy::FilterReflectedXSS;
377    bool modeDirectiveSeen = false;
378    bool reportDirectiveSeen = false;
379
380    while (1) {
381        // At end of previous directive: consume whitespace, semicolon, and whitespace.
382        if (!skipWhiteSpace(header, pos, false))
383            return result;
384
385        if (header[pos++] != ';') {
386            failureReason = failureReasonInvalidSeparator;
387            failurePosition = pos;
388            return ContentSecurityPolicy::ReflectedXSSInvalid;
389        }
390
391        if (!skipWhiteSpace(header, pos, false))
392            return result;
393
394        // At start of next directive.
395        if (skipToken(header, pos, "mode")) {
396            if (modeDirectiveSeen) {
397                failureReason = failureReasonDuplicateMode;
398                failurePosition = pos;
399                return ContentSecurityPolicy::ReflectedXSSInvalid;
400            }
401            modeDirectiveSeen = true;
402            if (!skipEquals(header, pos)) {
403                failureReason = failureReasonInvalidEquals;
404                failurePosition = pos;
405                return ContentSecurityPolicy::ReflectedXSSInvalid;
406            }
407            if (!skipToken(header, pos, "block")) {
408                failureReason = failureReasonInvalidMode;
409                failurePosition = pos;
410                return ContentSecurityPolicy::ReflectedXSSInvalid;
411            }
412            result = ContentSecurityPolicy::BlockReflectedXSS;
413        } else if (skipToken(header, pos, "report")) {
414            if (reportDirectiveSeen) {
415                failureReason = failureReasonDuplicateReport;
416                failurePosition = pos;
417                return ContentSecurityPolicy::ReflectedXSSInvalid;
418            }
419            reportDirectiveSeen = true;
420            if (!skipEquals(header, pos)) {
421                failureReason = failureReasonInvalidEquals;
422                failurePosition = pos;
423                return ContentSecurityPolicy::ReflectedXSSInvalid;
424            }
425            size_t startPos = pos;
426            if (!skipValue(header, pos)) {
427                failureReason = failureReasonInvalidReport;
428                failurePosition = pos;
429                return ContentSecurityPolicy::ReflectedXSSInvalid;
430            }
431            reportURL = header.substring(startPos, pos - startPos);
432            failurePosition = startPos; // If later semantic check deems unacceptable.
433        } else {
434            failureReason = failureReasonInvalidDirective;
435            failurePosition = pos;
436            return ContentSecurityPolicy::ReflectedXSSInvalid;
437        }
438    }
439}
440
441#if ENABLE(NOSNIFF)
442ContentTypeOptionsDisposition parseContentTypeOptionsHeader(const String& header)
443{
444    if (header.stripWhiteSpace().lower() == "nosniff")
445        return ContentTypeOptionsNosniff;
446    return ContentTypeOptionsNone;
447}
448#endif
449
450String extractReasonPhraseFromHTTPStatusLine(const String& statusLine)
451{
452    size_t spacePos = statusLine.find(' ');
453    // Remove status code from the status line.
454    spacePos = statusLine.find(' ', spacePos + 1);
455    return statusLine.substring(spacePos + 1);
456}
457
458XFrameOptionsDisposition parseXFrameOptionsHeader(const String& header)
459{
460    XFrameOptionsDisposition result = XFrameOptionsNone;
461
462    if (header.isEmpty())
463        return result;
464
465    Vector<String> headers;
466    header.split(',', headers);
467
468    for (size_t i = 0; i < headers.size(); i++) {
469        String currentHeader = headers[i].stripWhiteSpace();
470        XFrameOptionsDisposition currentValue = XFrameOptionsNone;
471        if (equalIgnoringCase(currentHeader, "deny"))
472            currentValue = XFrameOptionsDeny;
473        else if (equalIgnoringCase(currentHeader, "sameorigin"))
474            currentValue = XFrameOptionsSameOrigin;
475        else if (equalIgnoringCase(currentHeader, "allowall"))
476            currentValue = XFrameOptionsAllowAll;
477        else
478            currentValue = XFrameOptionsInvalid;
479
480        if (result == XFrameOptionsNone)
481            result = currentValue;
482        else if (result != currentValue)
483            return XFrameOptionsConflict;
484    }
485    return result;
486}
487
488bool parseRange(const String& range, long long& rangeOffset, long long& rangeEnd, long long& rangeSuffixLength)
489{
490    // The format of "Range" header is defined in RFC 2616 Section 14.35.1.
491    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
492    // We don't support multiple range requests.
493
494    rangeOffset = rangeEnd = rangeSuffixLength = -1;
495
496    // The "bytes" unit identifier should be present.
497    static const char bytesStart[] = "bytes=";
498    if (!range.startsWith(bytesStart, false))
499        return false;
500    String byteRange = range.substring(sizeof(bytesStart) - 1);
501
502    // The '-' character needs to be present.
503    int index = byteRange.find('-');
504    if (index == -1)
505        return false;
506
507    // If the '-' character is at the beginning, the suffix length, which specifies the last N bytes, is provided.
508    // Example:
509    //     -500
510    if (!index) {
511        String suffixLengthString = byteRange.substring(index + 1).stripWhiteSpace();
512        bool ok;
513        long long value = suffixLengthString.toInt64Strict(&ok);
514        if (ok)
515            rangeSuffixLength = value;
516        return true;
517    }
518
519    // Otherwise, the first-byte-position and the last-byte-position are provied.
520    // Examples:
521    //     0-499
522    //     500-
523    String firstBytePosStr = byteRange.left(index).stripWhiteSpace();
524    bool ok;
525    long long firstBytePos = firstBytePosStr.toInt64Strict(&ok);
526    if (!ok)
527        return false;
528
529    String lastBytePosStr = byteRange.substring(index + 1).stripWhiteSpace();
530    long long lastBytePos = -1;
531    if (!lastBytePosStr.isEmpty()) {
532        lastBytePos = lastBytePosStr.toInt64Strict(&ok);
533        if (!ok)
534            return false;
535    }
536
537    if (firstBytePos < 0 || !(lastBytePos == -1 || lastBytePos >= firstBytePos))
538        return false;
539
540    rangeOffset = firstBytePos;
541    rangeEnd = lastBytePos;
542    return true;
543}
544
545// HTTP/1.1 - RFC 2616
546// http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1
547// Request-Line = Method SP Request-URI SP HTTP-Version CRLF
548size_t parseHTTPRequestLine(const char* data, size_t length, String& failureReason, String& method, String& url, HTTPVersion& httpVersion)
549{
550    method = String();
551    url = String();
552    httpVersion = Unknown;
553
554    const char* space1 = 0;
555    const char* space2 = 0;
556    const char* p;
557    size_t consumedLength;
558
559    for (p = data, consumedLength = 0; consumedLength < length; p++, consumedLength++) {
560        if (*p == ' ') {
561            if (!space1)
562                space1 = p;
563            else if (!space2)
564                space2 = p;
565        } else if (*p == '\n')
566            break;
567    }
568
569    // Haven't finished header line.
570    if (consumedLength == length) {
571        failureReason = "Incomplete Request Line";
572        return 0;
573    }
574
575    // RequestLine does not contain 3 parts.
576    if (!space1 || !space2) {
577        failureReason = "Request Line does not appear to contain: <Method> <Url> <HTTPVersion>.";
578        return 0;
579    }
580
581    // The line must end with "\r\n".
582    const char* end = p + 1;
583    if (*(end - 2) != '\r') {
584        failureReason = "Request line does not end with CRLF";
585        return 0;
586    }
587
588    // Request Method.
589    method = String(data, space1 - data); // For length subtract 1 for space, but add 1 for data being the first character.
590
591    // Request URI.
592    url = String(space1 + 1, space2 - space1 - 1); // For length subtract 1 for space.
593
594    // HTTP Version.
595    String httpVersionString(space2 + 1, end - space2 - 3); // For length subtract 1 for space, and 2 for "\r\n".
596    if (httpVersionString.length() != 8 || !httpVersionString.startsWith("HTTP/1."))
597        httpVersion = Unknown;
598    else if (httpVersionString[7] == '0')
599        httpVersion = HTTP_1_0;
600    else if (httpVersionString[7] == '1')
601        httpVersion = HTTP_1_1;
602    else
603        httpVersion = Unknown;
604
605    return end - data;
606}
607
608size_t parseHTTPHeader(const char* start, size_t length, String& failureReason, String& nameStr, String& valueStr, bool strict)
609{
610    const char* p = start;
611    const char* end = start + length;
612
613    Vector<char> name;
614    Vector<char> value;
615    nameStr = String();
616    valueStr = String();
617
618    for (; p < end; p++) {
619        switch (*p) {
620        case '\r':
621            if (name.isEmpty()) {
622                if (p + 1 < end && *(p + 1) == '\n')
623                    return (p + 2) - start;
624                failureReason = "CR doesn't follow LF at " + trimInputSample(p, end - p);
625                return 0;
626            }
627            failureReason = "Unexpected CR in name at " + trimInputSample(name.data(), name.size());
628            return 0;
629        case '\n':
630            failureReason = "Unexpected LF in name at " + trimInputSample(name.data(), name.size());
631            return 0;
632        case ':':
633            break;
634        default:
635            name.append(*p);
636            continue;
637        }
638        if (*p == ':') {
639            ++p;
640            break;
641        }
642    }
643
644    for (; p < end && *p == 0x20; p++) { }
645
646    for (; p < end; p++) {
647        switch (*p) {
648        case '\r':
649            break;
650        case '\n':
651            if (strict) {
652                failureReason = "Unexpected LF in value at " + trimInputSample(value.data(), value.size());
653                return 0;
654            }
655            break;
656        default:
657            value.append(*p);
658        }
659        if (*p == '\r' || (!strict && *p == '\n')) {
660            ++p;
661            break;
662        }
663    }
664    if (p >= end || (strict && *p != '\n')) {
665        failureReason = "CR doesn't follow LF after value at " + trimInputSample(p, end - p);
666        return 0;
667    }
668    nameStr = String::fromUTF8(name.data(), name.size());
669    valueStr = String::fromUTF8(value.data(), value.size());
670    if (nameStr.isNull()) {
671        failureReason = "Invalid UTF-8 sequence in header name";
672        return 0;
673    }
674    if (valueStr.isNull()) {
675        failureReason = "Invalid UTF-8 sequence in header value";
676        return 0;
677    }
678    return p - start;
679}
680
681size_t parseHTTPRequestBody(const char* data, size_t length, Vector<unsigned char>& body)
682{
683    body.clear();
684    body.append(data, length);
685
686    return length;
687}
688
689}
690