1/*
2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 *           (C) 1999 Antti Koivisto (koivisto@kde.org)
4 *           (C) 2001 Dirk Mueller (mueller@kde.org)
5 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
6 *           (C) 2006 Alexey Proskuryakov (ap@nypop.com)
7 * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 * Library General Public License for more details.
18 *
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB.  If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23 */
24
25#include "config.h"
26#include "FormDataBuilder.h"
27
28#include "Blob.h"
29#include "Document.h"
30#include "Frame.h"
31#include "FrameLoader.h"
32#include "TextEncoding.h"
33
34#include <limits>
35#include <wtf/Assertions.h>
36#include <wtf/HexNumber.h>
37#include <wtf/text/CString.h>
38#include <wtf/RandomNumber.h>
39
40namespace WebCore {
41
42// Helper functions
43static inline void append(Vector<char>& buffer, char string)
44{
45    buffer.append(string);
46}
47
48static inline void append(Vector<char>& buffer, const char* string)
49{
50    buffer.append(string, strlen(string));
51}
52
53static inline void append(Vector<char>& buffer, const CString& string)
54{
55    buffer.append(string.data(), string.length());
56}
57
58static void appendQuotedString(Vector<char>& buffer, const CString& string)
59{
60    // Append a string as a quoted value, escaping quotes and line breaks.
61    // FIXME: Is it correct to use percent escaping here? Other browsers do not encode these characters yet,
62    // so we should test popular servers to find out if there is an encoding form they can handle.
63    size_t length = string.length();
64    for (size_t i = 0; i < length; ++i) {
65        char c = string.data()[i];
66
67        switch (c) {
68        case  0x0a:
69            append(buffer, "%0A");
70            break;
71        case 0x0d:
72            append(buffer, "%0D");
73            break;
74        case '"':
75            append(buffer, "%22");
76            break;
77        default:
78            append(buffer, c);
79        }
80    }
81}
82
83TextEncoding FormDataBuilder::encodingFromAcceptCharset(const String& acceptCharset, Document* document)
84{
85    String normalizedAcceptCharset = acceptCharset;
86    normalizedAcceptCharset.replace(',', ' ');
87
88    Vector<String> charsets;
89    normalizedAcceptCharset.split(' ', charsets);
90
91    TextEncoding encoding;
92
93    Vector<String>::const_iterator end = charsets.end();
94    for (Vector<String>::const_iterator it = charsets.begin(); it != end; ++it) {
95        if ((encoding = TextEncoding(*it)).isValid())
96            return encoding;
97    }
98
99    return document->inputEncoding();
100}
101
102Vector<char> FormDataBuilder::generateUniqueBoundaryString()
103{
104    Vector<char> boundary;
105
106    // The RFC 2046 spec says the alphanumeric characters plus the
107    // following characters are legal for boundaries:  '()+_,-./:=?
108    // However the following characters, though legal, cause some sites
109    // to fail: (),./:=+
110    // Note that our algorithm makes it twice as much likely for 'A' or 'B'
111    // to appear in the boundary string, because 0x41 and 0x42 are present in
112    // the below array twice.
113    static const char alphaNumericEncodingMap[64] = {
114        0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
115        0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
116        0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
117        0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
118        0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,
119        0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,
120        0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,
121        0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42
122    };
123
124    // Start with an informative prefix.
125    append(boundary, "----WebKitFormBoundary");
126
127    // Append 16 random 7bit ascii AlphaNumeric characters.
128    Vector<char> randomBytes;
129
130    for (unsigned i = 0; i < 4; ++i) {
131        unsigned randomness = static_cast<unsigned>(randomNumber() * (std::numeric_limits<unsigned>::max() + 1.0));
132        randomBytes.append(alphaNumericEncodingMap[(randomness >> 24) & 0x3F]);
133        randomBytes.append(alphaNumericEncodingMap[(randomness >> 16) & 0x3F]);
134        randomBytes.append(alphaNumericEncodingMap[(randomness >> 8) & 0x3F]);
135        randomBytes.append(alphaNumericEncodingMap[randomness & 0x3F]);
136    }
137
138    boundary.appendVector(randomBytes);
139    boundary.append(0); // Add a 0 at the end so we can use this as a C-style string.
140    return boundary;
141}
142
143void FormDataBuilder::beginMultiPartHeader(Vector<char>& buffer, const CString& boundary, const CString& name)
144{
145    addBoundaryToMultiPartHeader(buffer, boundary);
146
147    // FIXME: This loses data irreversibly if the input name includes characters you can't encode
148    // in the website's character set.
149    append(buffer, "Content-Disposition: form-data; name=\"");
150    appendQuotedString(buffer, name);
151    append(buffer, '"');
152}
153
154void FormDataBuilder::addBoundaryToMultiPartHeader(Vector<char>& buffer, const CString& boundary, bool isLastBoundary)
155{
156    append(buffer, "--");
157    append(buffer, boundary);
158
159    if (isLastBoundary)
160        append(buffer, "--");
161
162    append(buffer, "\r\n");
163}
164
165void FormDataBuilder::addFilenameToMultiPartHeader(Vector<char>& buffer, const TextEncoding& encoding, const String& filename)
166{
167    // FIXME: This loses data irreversibly if the filename includes characters you can't encode
168    // in the website's character set.
169    append(buffer, "; filename=\"");
170    appendQuotedString(buffer, encoding.encode(filename.characters(), filename.length(), QuestionMarksForUnencodables));
171    append(buffer, '"');
172}
173
174void FormDataBuilder::addContentTypeToMultiPartHeader(Vector<char>& buffer, const CString& mimeType)
175{
176    ASSERT(Blob::isNormalizedContentType(mimeType));
177    append(buffer, "\r\nContent-Type: ");
178    append(buffer, mimeType);
179}
180
181void FormDataBuilder::finishMultiPartHeader(Vector<char>& buffer)
182{
183    append(buffer, "\r\n\r\n");
184}
185
186void FormDataBuilder::addKeyValuePairAsFormData(Vector<char>& buffer, const CString& key, const CString& value, FormData::EncodingType encodingType)
187{
188    if (encodingType == FormData::TextPlain) {
189        if (!buffer.isEmpty())
190            append(buffer, "\r\n");
191        append(buffer, key);
192        append(buffer, '=');
193        append(buffer, value);
194    } else {
195        if (!buffer.isEmpty())
196            append(buffer, '&');
197        encodeStringAsFormData(buffer, key);
198        append(buffer, '=');
199        encodeStringAsFormData(buffer, value);
200    }
201}
202
203void FormDataBuilder::encodeStringAsFormData(Vector<char>& buffer, const CString& string)
204{
205    // Same safe characters as Netscape for compatibility.
206    static const char safeCharacters[] = "-._*";
207
208    // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
209    unsigned length = string.length();
210    for (unsigned i = 0; i < length; ++i) {
211        unsigned char c = string.data()[i];
212
213        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || strchr(safeCharacters, c))
214            append(buffer, c);
215        else if (c == ' ')
216            append(buffer, '+');
217        else if (c == '\n' || (c == '\r' && (i + 1 >= length || string.data()[i + 1] != '\n')))
218            append(buffer, "%0D%0A");
219        else if (c != '\r') {
220            append(buffer, '%');
221            appendByteAsHex(c, buffer);
222        }
223    }
224}
225
226}
227