1 | /* |
2 | * Copyright (C) 2009 Gustavo Noronha Silva |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public License |
15 | * along with this library; see the file COPYING.LIB. If not, write to |
16 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
17 | * Boston, MA 02110-1301, USA. |
18 | */ |
19 | |
20 | #include "config.h" |
21 | |
22 | #if USE(SOUP) |
23 | #include "ResourceRequest.h" |
24 | |
25 | #include "BlobData.h" |
26 | #include "BlobRegistryImpl.h" |
27 | #include "GUniquePtrSoup.h" |
28 | #include "HTTPParsers.h" |
29 | #include "MIMETypeRegistry.h" |
30 | #include "SharedBuffer.h" |
31 | #include "URLSoup.h" |
32 | #include "WebKitSoupRequestGeneric.h" |
33 | #include <wtf/text/CString.h> |
34 | #include <wtf/text/WTFString.h> |
35 | |
36 | namespace WebCore { |
37 | |
38 | static uint64_t appendEncodedBlobItemToSoupMessageBody(SoupMessage* soupMessage, const BlobDataItem& blobItem) |
39 | { |
40 | switch (blobItem.type()) { |
41 | case BlobDataItem::Type::Data: |
42 | soup_message_body_append(soupMessage->request_body, SOUP_MEMORY_TEMPORARY, blobItem.data().data()->data() + blobItem.offset(), blobItem.length()); |
43 | return blobItem.length(); |
44 | case BlobDataItem::Type::File: { |
45 | if (!blobItem.file()->expectedModificationTime()) |
46 | return 0; |
47 | |
48 | auto fileModificationTime = FileSystem::getFileModificationTime(blobItem.file()->path()); |
49 | if (!fileModificationTime) |
50 | return 0; |
51 | |
52 | if (fileModificationTime->secondsSinceEpoch().secondsAs<time_t>() != blobItem.file()->expectedModificationTime()->secondsSinceEpoch().secondsAs<time_t>()) |
53 | return 0; |
54 | |
55 | if (auto buffer = SharedBuffer::createWithContentsOfFile(blobItem.file()->path())) { |
56 | if (buffer->isEmpty()) |
57 | return 0; |
58 | |
59 | GUniquePtr<SoupBuffer> soupBuffer(buffer->createSoupBuffer(blobItem.offset(), blobItem.length() == BlobDataItem::toEndOfFile ? 0 : blobItem.length())); |
60 | if (soupBuffer->length) |
61 | soup_message_body_append_buffer(soupMessage->request_body, soupBuffer.get()); |
62 | return soupBuffer->length; |
63 | } |
64 | break; |
65 | } |
66 | } |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | void ResourceRequest::updateSoupMessageBody(SoupMessage* soupMessage) const |
72 | { |
73 | auto* formData = httpBody(); |
74 | if (!formData || formData->isEmpty()) |
75 | return; |
76 | |
77 | soup_message_body_set_accumulate(soupMessage->request_body, FALSE); |
78 | uint64_t bodySize = 0; |
79 | for (const auto& element : formData->elements()) { |
80 | switchOn(element.data, |
81 | [&] (const Vector<char>& bytes) { |
82 | bodySize += bytes.size(); |
83 | soup_message_body_append(soupMessage->request_body, SOUP_MEMORY_TEMPORARY, bytes.data(), bytes.size()); |
84 | }, [&] (const FormDataElement::EncodedFileData& fileData) { |
85 | if (auto buffer = SharedBuffer::createWithContentsOfFile(fileData.filename)) { |
86 | if (buffer->isEmpty()) |
87 | return; |
88 | |
89 | GUniquePtr<SoupBuffer> soupBuffer(buffer->createSoupBuffer()); |
90 | bodySize += buffer->size(); |
91 | if (soupBuffer->length) |
92 | soup_message_body_append_buffer(soupMessage->request_body, soupBuffer.get()); |
93 | } |
94 | }, [&] (const FormDataElement::EncodedBlobData& blob) { |
95 | if (auto* blobData = static_cast<BlobRegistryImpl&>(blobRegistry()).getBlobDataFromURL(blob.url)) { |
96 | for (const auto& item : blobData->items()) |
97 | bodySize += appendEncodedBlobItemToSoupMessageBody(soupMessage, item); |
98 | } |
99 | } |
100 | ); |
101 | } |
102 | |
103 | ASSERT(bodySize == static_cast<uint64_t>(soupMessage->request_body->length)); |
104 | } |
105 | |
106 | void ResourceRequest::updateSoupMessageMembers(SoupMessage* soupMessage) const |
107 | { |
108 | updateSoupMessageHeaders(soupMessage->request_headers); |
109 | |
110 | GUniquePtr<SoupURI> firstParty = urlToSoupURI(firstPartyForCookies()); |
111 | if (firstParty) |
112 | soup_message_set_first_party(soupMessage, firstParty.get()); |
113 | |
114 | soup_message_set_flags(soupMessage, m_soupFlags); |
115 | |
116 | if (!acceptEncoding()) |
117 | soup_message_disable_feature(soupMessage, SOUP_TYPE_CONTENT_DECODER); |
118 | if (!allowCookies()) |
119 | soup_message_disable_feature(soupMessage, SOUP_TYPE_COOKIE_JAR); |
120 | } |
121 | |
122 | void ResourceRequest::(SoupMessageHeaders* ) const |
123 | { |
124 | const HTTPHeaderMap& = httpHeaderFields(); |
125 | if (!headers.isEmpty()) { |
126 | HTTPHeaderMap::const_iterator end = headers.end(); |
127 | for (HTTPHeaderMap::const_iterator it = headers.begin(); it != end; ++it) |
128 | soup_message_headers_append(soupHeaders, it->key.utf8().data(), it->value.utf8().data()); |
129 | } |
130 | } |
131 | |
132 | void ResourceRequest::(SoupMessageHeaders* ) |
133 | { |
134 | m_httpHeaderFields.clear(); |
135 | SoupMessageHeadersIter ; |
136 | soup_message_headers_iter_init(&headersIter, soupHeaders); |
137 | const char* ; |
138 | const char* ; |
139 | while (soup_message_headers_iter_next(&headersIter, &headerName, &headerValue)) |
140 | m_httpHeaderFields.set(String(headerName), String(headerValue)); |
141 | } |
142 | |
143 | void ResourceRequest::updateSoupMessage(SoupMessage* soupMessage) const |
144 | { |
145 | g_object_set(soupMessage, SOUP_MESSAGE_METHOD, httpMethod().ascii().data(), NULL); |
146 | |
147 | GUniquePtr<SoupURI> uri = createSoupURI(); |
148 | soup_message_set_uri(soupMessage, uri.get()); |
149 | |
150 | updateSoupMessageMembers(soupMessage); |
151 | updateSoupMessageBody(soupMessage); |
152 | } |
153 | |
154 | void ResourceRequest::updateFromSoupMessage(SoupMessage* soupMessage) |
155 | { |
156 | bool shouldPortBeResetToZero = m_url.port() && !m_url.port().value(); |
157 | m_url = soupURIToURL(soup_message_get_uri(soupMessage)); |
158 | |
159 | // SoupURI cannot differeniate between an explicitly specified port 0 and |
160 | // no port specified. |
161 | if (shouldPortBeResetToZero) |
162 | m_url.setPort(0); |
163 | |
164 | m_httpMethod = String(soupMessage->method); |
165 | |
166 | updateFromSoupMessageHeaders(soupMessage->request_headers); |
167 | |
168 | if (soupMessage->request_body->data) |
169 | m_httpBody = FormData::create(soupMessage->request_body->data, soupMessage->request_body->length); |
170 | |
171 | if (SoupURI* firstParty = soup_message_get_first_party(soupMessage)) |
172 | m_firstPartyForCookies = soupURIToURL(firstParty); |
173 | |
174 | m_soupFlags = soup_message_get_flags(soupMessage); |
175 | |
176 | // FIXME: m_allowCookies should probably be handled here and on |
177 | // doUpdatePlatformRequest somehow. |
178 | } |
179 | |
180 | static const char* gSoupRequestInitiatingPageIDKey = "wk-soup-request-initiating-page-id" ; |
181 | |
182 | void ResourceRequest::updateSoupRequest(SoupRequest* soupRequest) const |
183 | { |
184 | if (m_initiatingPageID) { |
185 | uint64_t* initiatingPageIDPtr = static_cast<uint64_t*>(fastMalloc(sizeof(uint64_t))); |
186 | *initiatingPageIDPtr = m_initiatingPageID; |
187 | g_object_set_data_full(G_OBJECT(soupRequest), g_intern_static_string(gSoupRequestInitiatingPageIDKey), initiatingPageIDPtr, fastFree); |
188 | } |
189 | |
190 | if (WEBKIT_IS_SOUP_REQUEST_GENERIC(soupRequest)) |
191 | webkitSoupRequestGenericSetRequest(WEBKIT_SOUP_REQUEST_GENERIC(soupRequest), *this); |
192 | } |
193 | |
194 | void ResourceRequest::updateFromSoupRequest(SoupRequest* soupRequest) |
195 | { |
196 | uint64_t* initiatingPageIDPtr = static_cast<uint64_t*>(g_object_get_data(G_OBJECT(soupRequest), gSoupRequestInitiatingPageIDKey)); |
197 | m_initiatingPageID = initiatingPageIDPtr ? *initiatingPageIDPtr : 0; |
198 | } |
199 | |
200 | unsigned initializeMaximumHTTPConnectionCountPerHost() |
201 | { |
202 | // Soup has its own queue control; it wants to have all requests |
203 | // given to it, so that it is able to look ahead, and schedule |
204 | // them in a good way. |
205 | return 10000; |
206 | } |
207 | |
208 | GUniquePtr<SoupURI> ResourceRequest::createSoupURI() const |
209 | { |
210 | // WebKit does not support fragment identifiers in data URLs, but soup does. |
211 | // Before passing the URL to soup, we should make sure to urlencode any '#' |
212 | // characters, so that soup does not interpret them as fragment identifiers. |
213 | // See http://wkbug.com/68089 |
214 | if (m_url.protocolIsData()) { |
215 | String urlString = m_url.string(); |
216 | urlString.replace("#" , "%23" ); |
217 | return GUniquePtr<SoupURI>(soup_uri_new(urlString.utf8().data())); |
218 | } |
219 | |
220 | GUniquePtr<SoupURI> soupURI = urlToSoupURI(m_url); |
221 | |
222 | // Versions of libsoup prior to 2.42 have a soup_uri_new that will convert empty passwords that are not |
223 | // prefixed by a colon into null. Some parts of soup like the SoupAuthenticationManager will only be active |
224 | // when both the username and password are non-null. When we have credentials, empty usernames and passwords |
225 | // should be empty strings instead of null. |
226 | String urlUser = m_url.user(); |
227 | String urlPass = m_url.pass(); |
228 | if (!urlUser.isEmpty() || !urlPass.isEmpty()) { |
229 | soup_uri_set_user(soupURI.get(), urlUser.utf8().data()); |
230 | soup_uri_set_password(soupURI.get(), urlPass.utf8().data()); |
231 | } |
232 | |
233 | return soupURI; |
234 | } |
235 | |
236 | } |
237 | |
238 | #endif |
239 | |