1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2012 Intel Corporation. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "platform/network/ParsedContentType.h"
34
35#include "wtf/text/CString.h"
36#include "wtf/text/StringBuilder.h"
37
38namespace blink {
39
40class DummyParsedContentType {
41public:
42    void setContentType(const SubstringRange&) const { }
43    void setContentTypeParameter(const SubstringRange&, const SubstringRange&) const { }
44};
45
46static void skipSpaces(const String& input, unsigned& startIndex)
47{
48    while (startIndex < input.length() && input[startIndex] == ' ')
49        ++startIndex;
50}
51
52static SubstringRange parseParameterPart(const String& input, unsigned& startIndex)
53{
54    unsigned inputLength = input.length();
55    unsigned tokenStart = startIndex;
56    unsigned& tokenEnd = startIndex;
57
58    if (tokenEnd >= inputLength)
59        return SubstringRange();
60
61    bool quoted = input[tokenStart] == '\"';
62    bool escape = false;
63
64    while (tokenEnd < inputLength) {
65        UChar c = input[tokenEnd];
66        if (quoted && tokenStart != tokenEnd && c == '\"' && !escape)
67            return SubstringRange(tokenStart + 1, tokenEnd++ - tokenStart - 1);
68        if (!quoted && (c == ';' || c == '='))
69            return SubstringRange(tokenStart, tokenEnd - tokenStart);
70        escape = !escape && c == '\\';
71        ++tokenEnd;
72    }
73
74    if (quoted)
75        return SubstringRange();
76    return SubstringRange(tokenStart, tokenEnd - tokenStart);
77}
78
79static String substringForRange(const String& string, const SubstringRange& range)
80{
81    return string.substring(range.first, range.second);
82}
83
84// From http://tools.ietf.org/html/rfc2045#section-5.1:
85//
86// content := "Content-Type" ":" type "/" subtype
87//            *(";" parameter)
88//            ; Matching of media type and subtype
89//            ; is ALWAYS case-insensitive.
90//
91// type := discrete-type / composite-type
92//
93// discrete-type := "text" / "image" / "audio" / "video" /
94//                  "application" / extension-token
95//
96// composite-type := "message" / "multipart" / extension-token
97//
98// extension-token := ietf-token / x-token
99//
100// ietf-token := <An extension token defined by a
101//                standards-track RFC and registered
102//                with IANA.>
103//
104// x-token := <The two characters "X-" or "x-" followed, with
105//             no intervening white space, by any token>
106//
107// subtype := extension-token / iana-token
108//
109// iana-token := <A publicly-defined extension token. Tokens
110//                of this form must be registered with IANA
111//                as specified in RFC 2048.>
112//
113// parameter := attribute "=" value
114//
115// attribute := token
116//              ; Matching of attributes
117//              ; is ALWAYS case-insensitive.
118//
119// value := token / quoted-string
120//
121// token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
122//             or tspecials>
123//
124// tspecials :=  "(" / ")" / "<" / ">" / "@" /
125//               "," / ";" / ":" / "\" / <">
126//               "/" / "[" / "]" / "?" / "="
127//               ; Must be in quoted-string,
128//               ; to use within parameter values
129
130template <class ReceiverType>
131bool parseContentType(const String& contentType, ReceiverType& receiver)
132{
133    unsigned index = 0;
134    unsigned contentTypeLength = contentType.length();
135    skipSpaces(contentType, index);
136    if (index >= contentTypeLength)  {
137        WTF_LOG_ERROR("Invalid Content-Type string '%s'", contentType.ascii().data());
138        return false;
139    }
140
141    // There should not be any quoted strings until we reach the parameters.
142    size_t semiColonIndex = contentType.find(';', index);
143    if (semiColonIndex == kNotFound) {
144        receiver.setContentType(SubstringRange(index, contentTypeLength - index));
145        return true;
146    }
147
148    receiver.setContentType(SubstringRange(index, semiColonIndex - index));
149    index = semiColonIndex + 1;
150    while (true) {
151        skipSpaces(contentType, index);
152        SubstringRange keyRange = parseParameterPart(contentType, index);
153        if (!keyRange.second || index >= contentTypeLength) {
154            WTF_LOG_ERROR("Invalid Content-Type parameter name. (at %i)", index);
155            return false;
156        }
157
158        // Should we tolerate spaces here?
159        if (contentType[index++] != '=' || index >= contentTypeLength) {
160            WTF_LOG_ERROR("Invalid Content-Type malformed parameter (at %i).", index);
161            return false;
162        }
163
164        // Should we tolerate spaces here?
165        SubstringRange valueRange = parseParameterPart(contentType, index);
166
167        if (!valueRange.second) {
168            WTF_LOG_ERROR("Invalid Content-Type, invalid parameter value (at %i, for '%s').", index, substringForRange(contentType, keyRange).stripWhiteSpace().ascii().data());
169            return false;
170        }
171
172        // Should we tolerate spaces here?
173        if (index < contentTypeLength && contentType[index++] != ';') {
174            WTF_LOG_ERROR("Invalid Content-Type, invalid character at the end of key/value parameter (at %i).", index);
175            return false;
176        }
177
178        receiver.setContentTypeParameter(keyRange, valueRange);
179
180        if (index >= contentTypeLength)
181            return true;
182    }
183
184    return true;
185}
186
187bool isValidContentType(const String& contentType)
188{
189    if (contentType.contains('\r') || contentType.contains('\n'))
190        return false;
191
192    DummyParsedContentType parsedContentType = DummyParsedContentType();
193    return parseContentType<DummyParsedContentType>(contentType, parsedContentType);
194}
195
196ParsedContentType::ParsedContentType(const String& contentType)
197    : m_contentType(contentType.stripWhiteSpace())
198{
199    parseContentType<ParsedContentType>(m_contentType, *this);
200}
201
202String ParsedContentType::charset() const
203{
204    return parameterValueForName("charset");
205}
206
207String ParsedContentType::parameterValueForName(const String& name) const
208{
209    return m_parameters.get(name);
210}
211
212size_t ParsedContentType::parameterCount() const
213{
214    return m_parameters.size();
215}
216
217void ParsedContentType::setContentType(const SubstringRange& contentRange)
218{
219    m_mimeType = substringForRange(m_contentType, contentRange).stripWhiteSpace();
220}
221
222void ParsedContentType::setContentTypeParameter(const SubstringRange& key, const SubstringRange& value)
223{
224    m_parameters.set(substringForRange(m_contentType, key), substringForRange(m_contentType, value));
225}
226
227}
228