1e3d87d78000f37b3c645adb8f87cefa92d0b1b0
[WebKit-https.git] / Source / WebCore / Modules / fetch / FetchResponse.cpp
1 /*
2  * Copyright (C) 2016 Canon Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted, provided that the following conditions
6  * are required to be met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer.
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution.
13  * 3.  Neither the name of Canon Inc. nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
21  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "FetchResponse.h"
31
32 #if ENABLE(FETCH_API)
33
34 #include "Dictionary.h"
35 #include "ExceptionCode.h"
36 #include "FetchRequest.h"
37 #include "JSFetchResponse.h"
38 #include "ScriptExecutionContext.h"
39
40 namespace WebCore {
41
42 static inline bool isRedirectStatus(int status)
43 {
44     return status == 301 || status == 302 || status == 303 || status == 307 || status == 308;
45 }
46
47 static inline bool isNullBodyStatus(int status)
48 {
49     return status == 101 || status == 204 || status == 205 || status == 304;
50 }
51
52 Ref<FetchResponse> FetchResponse::error(ScriptExecutionContext& context)
53 {
54     return adoptRef(*new FetchResponse(context, Type::Error, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));
55 }
56
57 RefPtr<FetchResponse> FetchResponse::redirect(ScriptExecutionContext& context, const String& url, int status, ExceptionCode& ec)
58 {
59     // FIXME: Tighten the URL parsing algorithm according https://url.spec.whatwg.org/#concept-url-parser.
60     URL requestURL = context.completeURL(url);
61     if (!requestURL.isValid() || !requestURL.user().isEmpty() || !requestURL.pass().isEmpty()) {
62         ec = TypeError;
63         return nullptr;
64     }
65     if (!isRedirectStatus(status)) {
66         ec = TypeError;
67         return nullptr;
68     }
69     RefPtr<FetchResponse> redirectResponse = adoptRef(*new FetchResponse(context, Type::Default, { }, FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));
70     redirectResponse->m_response.setHTTPStatusCode(status);
71     redirectResponse->m_headers->fastSet(HTTPHeaderName::Location, requestURL.string());
72     return redirectResponse;
73 }
74
75 void FetchResponse::initializeWith(const Dictionary& init, ExceptionCode& ec)
76 {
77     int status;
78     if (!init.get("status", status)) {
79         ec = TypeError;
80         return;
81     }
82     if (status < 200 || status > 599) {
83         ec = RangeError;
84         return;
85     }
86
87     // FIXME: Validate reason phrase (https://tools.ietf.org/html/rfc7230#section-3.1.2).
88     String statusText;
89     if (!init.get("statusText", statusText)) {
90         ec = TypeError;
91         return;
92     }
93     m_response.setHTTPStatusCode(status);
94     m_response.setHTTPStatusText(statusText);
95
96     RefPtr<FetchHeaders> initialHeaders;
97     if (init.get("headers", initialHeaders))
98         m_headers->fill(initialHeaders.get());
99
100     JSC::JSValue body;
101     if (init.get("body", body)) {
102         if (isNullBodyStatus(status)) {
103             ec = TypeError;
104             return;
105         }
106         m_body = FetchBody::extract(*init.execState(), body);
107         if (m_headers->fastGet(HTTPHeaderName::ContentType).isEmpty() && !m_body.mimeType().isEmpty())
108             m_headers->fastSet(HTTPHeaderName::ContentType, m_body.mimeType());
109     }
110 }
111
112 FetchResponse::FetchResponse(ScriptExecutionContext& context, Type type, FetchBody&& body, Ref<FetchHeaders>&& headers, ResourceResponse&& response)
113     : FetchBodyOwner(context, WTFMove(body))
114     , m_type(type)
115     , m_response(WTFMove(response))
116     , m_headers(WTFMove(headers))
117 {
118 }
119
120 RefPtr<FetchResponse> FetchResponse::clone(ScriptExecutionContext& context, ExceptionCode& ec)
121 {
122     if (isDisturbed() || m_isLocked) {
123         ec = TypeError;
124         return nullptr;
125     }
126     RefPtr<FetchResponse> cloned = adoptRef(*new FetchResponse(context, m_type, FetchBody(m_body), FetchHeaders::create(headers()), ResourceResponse(m_response)));
127     cloned->m_isRedirected = m_isRedirected;
128     return cloned;
129 }
130
131 String FetchResponse::type() const
132 {
133     switch (m_type) {
134     case Type::Basic:
135         return ASCIILiteral("basic");
136     case Type::Cors:
137         return ASCIILiteral("cors");
138     case Type::Default:
139         return ASCIILiteral("default");
140     case Type::Error:
141         return ASCIILiteral("error");
142     case Type::Opaque:
143         return ASCIILiteral("opaque");
144     case Type::OpaqueRedirect:
145         return ASCIILiteral("opaqueredirect");
146     };
147     ASSERT_NOT_REACHED();
148     return String();
149 }
150
151 // FIXME: Implement this, as a custom or through binding generator.
152 JSC::JSValue JSFetchResponse::body(JSC::ExecState&) const
153 {
154     return JSC::jsNull();
155 }
156
157 void FetchResponse::startFetching(ScriptExecutionContext& context, const FetchRequest& request, FetchPromise&& promise)
158 {
159     Ref<FetchResponse> response = adoptRef(*new FetchResponse(context, Type::Basic, FetchBody::loadingBody(), FetchHeaders::create(FetchHeaders::Guard::Immutable), ResourceResponse()));
160
161     // Setting pending activity until BodyLoader didFail or didSucceed callback is called.
162     response->setPendingActivity(response.ptr());
163
164     response->m_bodyLoader = BodyLoader(response.get(), WTFMove(promise));
165     if (!response->m_bodyLoader->start(context, request))
166         response->m_bodyLoader = Nullopt;
167 }
168
169 void FetchResponse::fetch(ScriptExecutionContext& context, FetchRequest& input, const Dictionary& dictionary, FetchPromise&& promise)
170 {
171     ExceptionCode ec = 0;
172     RefPtr<FetchRequest> fetchRequest = FetchRequest::create(context, input, dictionary, ec);
173     if (ec) {
174         promise.reject(ec);
175         return;
176     }
177     ASSERT(fetchRequest);
178     startFetching(context, *fetchRequest, WTFMove(promise));
179 }
180
181 void FetchResponse::fetch(ScriptExecutionContext& context, const String& url, const Dictionary& dictionary, FetchPromise&& promise)
182 {
183     ExceptionCode ec = 0;
184     RefPtr<FetchRequest> fetchRequest = FetchRequest::create(context, url, dictionary, ec);
185     if (ec) {
186         promise.reject(ec);
187         return;
188     }
189     ASSERT(fetchRequest);
190     startFetching(context, *fetchRequest, WTFMove(promise));
191 }
192
193 void FetchResponse::BodyLoader::didSucceed()
194 {
195     m_response.m_bodyLoader = Nullopt;
196     m_response.unsetPendingActivity(&m_response);
197 }
198
199 void FetchResponse::BodyLoader::didFail()
200 {
201     if (m_promise)
202         std::exchange(m_promise, Nullopt)->reject(TypeError);
203
204     // Check whether didFail is called as part of FetchLoader::start.
205     if (m_loader->isStarted())
206         m_response.m_bodyLoader = Nullopt;
207
208     // FIXME: Handle the case of failing after didReceiveResponse is called.
209
210     m_response.unsetPendingActivity(&m_response);
211 }
212
213 FetchResponse::BodyLoader::BodyLoader(FetchResponse& response, FetchPromise&& promise)
214     : m_response(response)
215     , m_promise(WTFMove(promise))
216 {
217 }
218
219 void FetchResponse::BodyLoader::didReceiveResponse(const ResourceResponse& resourceResponse)
220 {
221     ASSERT(m_promise);
222
223     m_response.m_response = resourceResponse;
224     m_response.m_headers->filterAndFill(resourceResponse.httpHeaderFields(), FetchHeaders::Guard::Response);
225
226     std::exchange(m_promise, Nullopt)->resolve(&m_response);
227 }
228
229 void FetchResponse::BodyLoader::didFinishLoadingAsArrayBuffer(RefPtr<ArrayBuffer>&& buffer)
230 {
231     m_response.body().loadedAsArrayBuffer(WTFMove(buffer));
232 }
233
234 bool FetchResponse::BodyLoader::start(ScriptExecutionContext& context, const FetchRequest& request)
235 {
236     m_loader = std::make_unique<FetchLoader>(FetchLoader::Type::ArrayBuffer, *this);
237     m_loader->start(context, request);
238     return m_loader->isStarted();
239 }
240
241 void FetchResponse::BodyLoader::stop()
242 {
243     if (m_loader)
244         m_loader->stop();
245 }
246
247 void FetchResponse::stop()
248 {
249     FetchBodyOwner::stop();
250     if (m_bodyLoader) {
251         RefPtr<FetchResponse> protect(this);
252         m_bodyLoader->stop();
253         m_bodyLoader = Nullopt;
254     }
255 }
256
257 const char* FetchResponse::activeDOMObjectName() const
258 {
259     return "Response";
260 }
261
262 bool FetchResponse::canSuspendForDocumentSuspension() const
263 {
264     // FIXME: We can probably do the same strategy as XHR.
265     return !isActive();
266 }
267
268 } // namespace WebCore
269
270 #endif // ENABLE(FETCH_API)