summaryrefslogtreecommitdiffstats
path: root/third_party/WebKit/Source/core/fetch/FetchUtils.cpp
blob: 658f3ad2682d4f8ae986e2876a441a095fb0e5f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "core/fetch/FetchUtils.h"

#include "platform/network/HTTPHeaderMap.h"
#include "platform/network/HTTPParsers.h"
#include "wtf/HashSet.h"
#include "wtf/Threading.h"
#include "wtf/text/AtomicString.h"
#include "wtf/text/WTFString.h"

namespace blink {

namespace {

bool isHTTPWhitespace(UChar chr)
{
    return chr == ' ' || chr == '\n' || chr == '\t' || chr == '\r';
}

class ForbiddenHeaderNames {
    WTF_MAKE_NONCOPYABLE(ForbiddenHeaderNames); USING_FAST_MALLOC(ForbiddenHeaderNames);
public:
    bool has(const String& name) const
    {
        return m_fixedNames.contains(name)
            || name.startsWith(m_proxyHeaderPrefix, TextCaseInsensitive)
            || name.startsWith(m_secHeaderPrefix, TextCaseInsensitive);
    }

    static const ForbiddenHeaderNames& get();

private:
    ForbiddenHeaderNames();

    String m_proxyHeaderPrefix;
    String m_secHeaderPrefix;
    HashSet<String, CaseFoldingHash> m_fixedNames;
};

ForbiddenHeaderNames::ForbiddenHeaderNames()
    : m_proxyHeaderPrefix("proxy-")
    , m_secHeaderPrefix("sec-")
{
    m_fixedNames.add("accept-charset");
    m_fixedNames.add("accept-encoding");
    m_fixedNames.add("access-control-request-headers");
    m_fixedNames.add("access-control-request-method");
    m_fixedNames.add("connection");
    m_fixedNames.add("content-length");
    m_fixedNames.add("cookie");
    m_fixedNames.add("cookie2");
    m_fixedNames.add("date");
    m_fixedNames.add("dnt");
    m_fixedNames.add("expect");
    m_fixedNames.add("host");
    m_fixedNames.add("keep-alive");
    m_fixedNames.add("origin");
    m_fixedNames.add("referer");
    m_fixedNames.add("te");
    m_fixedNames.add("trailer");
    m_fixedNames.add("transfer-encoding");
    m_fixedNames.add("upgrade");
    m_fixedNames.add("user-agent");
    m_fixedNames.add("via");
}

const ForbiddenHeaderNames& ForbiddenHeaderNames::get()
{
    DEFINE_THREAD_SAFE_STATIC_LOCAL(const ForbiddenHeaderNames, instance, new ForbiddenHeaderNames);
    return instance;
}

} // namespace

bool FetchUtils::isSimpleMethod(const String& method)
{
    // http://fetch.spec.whatwg.org/#simple-method
    // "A simple method is a method that is `GET`, `HEAD`, or `POST`."
    return method == "GET" || method == "HEAD" || method == "POST";
}

bool FetchUtils::isSimpleHeader(const AtomicString& name, const AtomicString& value)
{
    // http://fetch.spec.whatwg.org/#simple-header
    // "A simple header is a header whose name is either one of `Accept`,
    // `Accept-Language`, and `Content-Language`, or whose name is
    // `Content-Type` and value, once parsed, is one of
    // `application/x-www-form-urlencoded`, `multipart/form-data`, and
    // `text/plain`."
    // Treat 'Save-Data' as a simple header, since it is added by Chrome when
    // Data Saver feature is enabled.

    if (equalIgnoringCase(name, "accept")
        || equalIgnoringCase(name, "accept-language")
        || equalIgnoringCase(name, "content-language")
        || equalIgnoringCase(name, "save-data"))
        return true;

    if (equalIgnoringCase(name, "content-type")) {
        AtomicString mimeType = extractMIMETypeFromMediaType(value);
        return equalIgnoringCase(mimeType, "application/x-www-form-urlencoded")
            || equalIgnoringCase(mimeType, "multipart/form-data")
            || equalIgnoringCase(mimeType, "text/plain");
    }

    return false;
}

bool FetchUtils::isSimpleRequest(const String& method, const HTTPHeaderMap& headerMap)
{
    if (!isSimpleMethod(method))
        return false;

    for (const auto& header : headerMap) {
        // Preflight is required for MIME types that can not be sent via form
        // submission.
        if (!isSimpleHeader(header.key, header.value))
            return false;
    }

    return true;
}

bool FetchUtils::isForbiddenMethod(const String& method)
{
    // http://fetch.spec.whatwg.org/#forbidden-method
    // "A forbidden method is a method that is a byte case-insensitive match"
    //  for one of `CONNECT`, `TRACE`, and `TRACK`."
    return equalIgnoringCase(method, "TRACE")
        || equalIgnoringCase(method, "TRACK")
        || equalIgnoringCase(method, "CONNECT");
}

bool FetchUtils::isForbiddenHeaderName(const String& name)
{
    // http://fetch.spec.whatwg.org/#forbidden-header-name
    // "A forbidden header name is a header names that is one of:
    //   `Accept-Charset`, `Accept-Encoding`, `Access-Control-Request-Headers`,
    //   `Access-Control-Request-Method`, `Connection`,
    //   `Content-Length, Cookie`, `Cookie2`, `Date`, `DNT`, `Expect`, `Host`,
    //   `Keep-Alive`, `Origin`, `Referer`, `TE`, `Trailer`,
    //   `Transfer-Encoding`, `Upgrade`, `User-Agent`, `Via`
    // or starts with `Proxy-` or `Sec-` (including when it is just `Proxy-` or
    // `Sec-`)."

    return ForbiddenHeaderNames::get().has(name);
}

bool FetchUtils::isForbiddenResponseHeaderName(const String& name)
{
    // http://fetch.spec.whatwg.org/#forbidden-response-header-name
    // "A forbidden response header name is a header name that is one of:
    // `Set-Cookie`, `Set-Cookie2`"

    return equalIgnoringCase(name, "set-cookie") || equalIgnoringCase(name, "set-cookie2");
}

bool FetchUtils::isSimpleOrForbiddenRequest(const String& method, const HTTPHeaderMap& headerMap)
{
    if (!isSimpleMethod(method))
        return false;

    for (const auto& header : headerMap) {
        if (!isSimpleHeader(header.key, header.value) && !isForbiddenHeaderName(header.key))
            return false;
    }

    return true;
}

AtomicString FetchUtils::normalizeMethod(const AtomicString& method)
{
    // https://fetch.spec.whatwg.org/#concept-method-normalize

    // We place GET and POST first because they are more commonly used than
    // others.
    const char* const methods[] = {
        "GET",
        "POST",
        "DELETE",
        "HEAD",
        "OPTIONS",
        "PUT",
    };

    for (const auto& known : methods) {
        if (equalIgnoringCase(method, known)) {
            // Don't bother allocating a new string if it's already all
            // uppercase.
            return method == known ? method : known;
        }
    }
    return method;
}

String FetchUtils::normalizeHeaderValue(const String& value)
{
    // https://fetch.spec.whatwg.org/#concept-header-value-normalize
    // Strip leading and trailing whitespace from header value.
    // HTTP whitespace bytes are 0x09, 0x0A, 0x0D, and 0x20.

    return value.stripWhiteSpace(isHTTPWhitespace);
}

} // namespace blink