1 // Copyright 2006, Google Inc.
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are met:
6 // 1. Redistributions of source code must retain the above copyright notice,
7 // this list of conditions and the following disclaimer.
8 // 2. Redistributions in binary form must reproduce the above copyright notice,
9 // this list of conditions and the following disclaimer in the documentation
10 // and/or other materials provided with the distribution.
11 // 3. Neither the name of Google Inc. nor the names of its contributors may be
12 // used to endorse or promote products derived from this software without
13 // specific prior written permission.
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18 // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include "gears/httprequest/ie/httprequest_ie.h"
32 #include "gears/base/common/string16.h"
33 #include "gears/base/common/string_utils.h"
34 #include "gears/base/common/url_utils.h"
35 #include "gears/base/ie/activex_utils.h"
36 #include "gears/blob/blob_ie.h"
37 #include "gears/blob/buffer_blob.h"
38 #include "gears/localserver/common/http_constants.h"
42 static const char16
*kRequestFailedError
= STRING16(L
"The request failed.");
43 static const char16
*kInternalError
= STRING16(L
"Internal error.");
44 static const char16
*kAlreadyOpenError
= STRING16(L
"Request is already open.");
45 static const char16
*kNotOpenError
= STRING16(L
"Request is not open.");
46 static const char16
*kNotInteractiveError
=
47 STRING16(L
"Request is not loading or done.");
50 GearsHttpRequest::GearsHttpRequest()
51 : request_(NULL
), content_type_header_was_set_(false),
52 has_fired_completion_event_(false) {
56 GearsHttpRequest::~GearsHttpRequest() {
61 STDMETHODIMP
GearsHttpRequest::put_onreadystatechange(
62 /* [in] */ VARIANT
*handler
) {
63 IDispatch
*handler_dispatch
= NULL
;
64 if (!ActiveXUtils::VariantIsNullOrUndefined(handler
)) {
65 if (handler
->vt
== VT_DISPATCH
) {
66 handler_dispatch
= handler
->pdispVal
;
68 RETURN_EXCEPTION(STRING16(
69 L
"The onmesonreadystatechangesage callback must be a function."));
72 onreadystatechangehandler_
= handler_dispatch
;
77 STDMETHODIMP
GearsHttpRequest::get_onreadystatechange(
78 /* [retval][out] */ VARIANT
*handler
){
79 ::VariantClear(handler
);
80 if (onreadystatechangehandler_
) {
81 handler
->vt
= VT_DISPATCH
;
82 handler
->pdispVal
= onreadystatechangehandler_
;
83 handler
->pdispVal
->AddRef(); // the caller must release
89 STDMETHODIMP
GearsHttpRequest::get_readyState(
90 /* [retval][out] */ int *state
) {
91 if (!state
) return E_POINTER
;
97 STDMETHODIMP
GearsHttpRequest::open(
98 /* [in] */ const BSTR method
,
99 /* [in] */ const BSTR url
,
100 /* [optional][in] */ const VARIANT
*async
) {
101 if (!method
|| !url
) return E_POINTER
;
106 if (!IsUninitialized()) {
107 RETURN_EXCEPTION(kAlreadyOpenError
);
111 RETURN_EXCEPTION(STRING16(L
"The method parameter must be a string."));
114 RETURN_EXCEPTION(STRING16(L
"The url parameter must be a string."));
117 std::string16 full_url
;
118 std::string16 exception_message
;
119 if (!ResolveUrl(url
, &full_url
, &exception_message
))
120 RETURN_EXCEPTION(exception_message
.c_str());
124 if (unload_monitor_
== NULL
) {
125 unload_monitor_
.reset(new JsEventMonitor(GetJsRunner(), JSEVENT_UNLOAD
,
129 content_type_header_was_set_
= false;
130 has_fired_completion_event_
= false;
132 if (!request_
->Open(method
, full_url
.c_str(), true)) {
133 RETURN_EXCEPTION(kInternalError
);
140 void GearsHttpRequest::HandleEvent(JsEventType event_type
) {
141 assert(event_type
== JSEVENT_UNLOAD
);
142 onreadystatechangehandler_
.Release();
143 unload_monitor_
.reset(NULL
);
148 static bool IsDisallowedHeader(const char16
*header
) {
149 // Headers which cannot be set according to the w3c spec
150 static const char16
* kDisallowedHeaders
[] = {
151 STRING16(L
"Accept-Charset"),
152 STRING16(L
"Accept-Encoding"),
153 STRING16(L
"Connection"),
154 STRING16(L
"Content-Length"),
155 STRING16(L
"Content-Transfer-Encoding"),
159 STRING16(L
"Keep-Alive"),
160 STRING16(L
"Referer"),
162 STRING16(L
"Trailer"),
163 STRING16(L
"Transfer-Encoding"),
164 STRING16(L
"Upgrade"),
166 for (int i
= 0; i
< static_cast<int>(ARRAYSIZE(kDisallowedHeaders
)); ++i
) {
167 if (StringCompareIgnoreCase(header
, kDisallowedHeaders
[i
]) == 0)
174 STDMETHODIMP
GearsHttpRequest::setRequestHeader(
175 /* [in] */ const BSTR name
,
176 /* [in] */ const BSTR value
) {
177 if (!name
) return E_POINTER
;
179 RETURN_EXCEPTION(kNotOpenError
);
181 if (IsDisallowedHeader(name
)) {
182 RETURN_EXCEPTION(STRING16(L
"This header may not be set."));
184 if (!request_
->SetRequestHeader(name
, value
? value
: L
"")) {
185 RETURN_EXCEPTION(kInternalError
);
187 if (StringCompareIgnoreCase(name
, HttpConstants::kContentTypeHeader
) == 0) {
188 content_type_header_was_set_
= true;
194 STDMETHODIMP
GearsHttpRequest::send(
195 /* [optional][in] */ const VARIANT
*data
) {
197 RETURN_EXCEPTION(kNotOpenError
);
199 const char16
*post_data_str
= NULL
;
200 if (ActiveXUtils::OptionalVariantIsPresent(data
)) {
201 if (data
->vt
!= VT_BSTR
) {
202 RETURN_EXCEPTION(STRING16(L
"Data parameter must be a string."));
204 post_data_str
= data
->bstrVal
;
207 HttpRequest
*request_being_sent
= request_
;
210 if (post_data_str
&& post_data_str
[0]) {
211 if (!content_type_header_was_set_
) {
212 request_
->SetRequestHeader(HttpConstants::kContentTypeHeader
,
213 HttpConstants::kMimeTextPlain
);
215 ok
= request_
->SendString(post_data_str
);
217 ok
= request_
->Send();
221 if (!has_fired_completion_event_
) {
222 // We only throw here if we haven't surfaced the error through
223 // an onreadystatechange callback. Since the JS code for
224 // xhr.onreadystatechange might call xhr.open(), check whether
225 // 'request_' has changed, which indicates that happened.
226 // Also, we don't trust IsComplete() to indicate that we actually
227 // fired the event, the underlying C++ object *may* declare itself
228 // complete without having called our callback. We're being defensive.
229 if (request_
== request_being_sent
) {
230 onreadystatechangehandler_
.Release();
231 RETURN_EXCEPTION(kInternalError
);
239 STDMETHODIMP
GearsHttpRequest::abort() {
241 request_
->SetOnReadyStateChange(NULL
);
249 bool GearsHttpRequest::IsValidResponse() {
250 assert(IsInteractive() || IsComplete());
251 int status_code
= -1;
252 if (!request_
->GetStatus(&status_code
))
254 return ::IsValidResponseCode(status_code
);
258 STDMETHODIMP
GearsHttpRequest::getAllResponseHeaders(
259 /* [retval][out] */ BSTR
*headers
) {
260 if (!headers
) return E_POINTER
;
262 if (!(IsInteractive() || IsComplete()))
263 RETURN_EXCEPTION(kNotInteractiveError
);
264 if (!IsValidResponse()) {
265 *headers
= NULL
; // NULL means empty string
269 std::string16 headers_str
;
270 if (!request_
->GetAllResponseHeaders(&headers_str
))
271 RETURN_EXCEPTION(kInternalError
);
273 CComBSTR
headers_bstr(headers_str
.c_str());
274 *headers
= headers_bstr
.Detach();
279 STDMETHODIMP
GearsHttpRequest::getResponseHeader(
280 /* [in] */ const BSTR header_name
,
281 /* [retval][out] */ BSTR
*header_value
) {
282 if (!header_name
|| !header_value
) return E_POINTER
;
284 if (!(IsInteractive() || IsComplete()))
285 RETURN_EXCEPTION(kNotInteractiveError
);
286 if (!IsValidResponse()) {
287 *header_value
= NULL
;
290 std::string16 value_str
;
291 if (!request_
->GetResponseHeader(header_name
, &value_str
))
292 RETURN_EXCEPTION(kInternalError
);
294 CComBSTR
value_bstr(value_str
.c_str());
295 *header_value
= value_bstr
.Detach();
300 STDMETHODIMP
GearsHttpRequest::get_responseText(
301 /* [retval][out] */ BSTR
*body
) {
302 if (!body
) return E_POINTER
;
304 if (!(IsInteractive() || IsComplete()))
305 RETURN_EXCEPTION(kNotInteractiveError
);
306 if (!IsValidResponse()) {
312 // Don't cache incomplete bodies
313 std::string16 body_str
;
314 request_
->GetResponseBodyAsText(&body_str
);
315 CComBSTR
body_bstr(body_str
.c_str());
316 *body
= body_bstr
.Detach();
320 if (response_text_
== NULL
) {
322 response_text_
.reset(new std::string16
);
323 request_
->GetResponseBodyAsText(response_text_
.get());
326 LOG16((L
"GearsHttpRequest::get_responseText - %d chars\n",
327 response_text_
->length()));
329 // Return the cached body
330 CComBSTR
body_bstr(response_text_
->c_str());
331 *body
= body_bstr
.Detach();
336 #ifdef OFFICIAL_BUILD
337 // Blob support is not ready for prime time yet
339 STDMETHODIMP
GearsHttpRequest::get_responseBlob(
340 /* [retval][out] */ IUnknown
**blob
) {
342 RETURN_EXCEPTION(STRING16(L
"responseBlob is not supported before request "
345 if (response_blob_
== NULL
) {
346 // Not already cached - make a new blob and copy the contents in
347 CComObject
<GearsBlob
> *blob_com
;
348 HRESULT hr
= CComObject
<GearsBlob
>::CreateInstance(&blob_com
);
350 RETURN_EXCEPTION(STRING16(L
"Could not create GearsBlob."));
352 scoped_ptr
<CComObject
<GearsBlob
> > blob_ptr(blob_com
);
353 if (IsValidResponse()) {
354 // GetResponseBody() destroys the data, so get a copy for response_text_
355 if (response_text_
== NULL
) {
356 response_text_
.reset(new std::string16
);
357 request_
->GetResponseBodyAsText(response_text_
.get());
360 std::vector
<uint8
> *body
= request_
->GetResponseBody();
362 blob_ptr
->Reset(new BufferBlob(body
));
365 // else blob_ptr stays empty
367 if (!blob_ptr
->InitBaseFromSibling(this)) {
368 RETURN_EXCEPTION(STRING16(L
"Initializing base class failed."));
370 response_blob_
= blob_ptr
.release();
373 *blob
= response_blob_
;
377 #endif // not OFFICIAL_BUILD
380 STDMETHODIMP
GearsHttpRequest::get_status(
381 /* [retval][out] */ int *status_code
) {
382 if (!status_code
) return E_POINTER
;
384 if (!(IsInteractive() || IsComplete()))
385 RETURN_EXCEPTION(kNotInteractiveError
);
387 if (!request_
->GetStatus(status_code
))
388 RETURN_EXCEPTION(kInternalError
);
390 if (!IsValidResponseCode(*status_code
))
391 RETURN_EXCEPTION(kRequestFailedError
);
397 STDMETHODIMP
GearsHttpRequest::get_statusText(
398 /* [retval][out] */ BSTR
*status_text
) {
399 if (!status_text
) return E_POINTER
;
401 if (!(IsInteractive() || IsComplete()))
402 RETURN_EXCEPTION(kNotInteractiveError
);
403 if (!IsValidResponse())
404 RETURN_EXCEPTION(kRequestFailedError
);
406 std::string16 status_str
;
407 if (!request_
->GetStatusText(&status_str
))
408 RETURN_EXCEPTION(kInternalError
);
409 CComBSTR
status_bstr(status_str
.c_str());
410 *status_text
= status_bstr
.Detach();
415 void GearsHttpRequest::DataAvailable(HttpRequest
*source
) {
416 assert(source
== request_
);
417 LOG16((L
"GearsHttpRequest::DataAvailable\n"));
418 ReadyStateChanged(source
);
422 void GearsHttpRequest::ReadyStateChanged(HttpRequest
*source
) {
423 assert(source
== request_
);
424 if (onreadystatechangehandler_
) {
426 DISPPARAMS dispparams
= {0};
428 // To remove cyclic dependencies we drop our reference to the
429 // callback when the request is complete.
430 CComPtr
<IDispatch
> dispatch
= onreadystatechangehandler_
;
432 has_fired_completion_event_
= true;
433 onreadystatechangehandler_
.Release();
436 // TODO(michaeln): The JavaScript 'this' pointer should be a reference
437 // to the HttpRequest object. This is not currently the case.
439 // Note: strangely, the parameters passed thru IDispatch(Ex) are in
440 // reverse order as compared to the method signature being invoked
441 CComQIPtr
<IDispatchEx
> dispatchex
= dispatch
;
443 // We prefer to call things thru IDispatchEx in order to use DISPID_THIS
444 // such that the closure is scoped properly.
445 DISPID disp_this
= DISPID_THIS
;
447 var
[0].vt
= VT_DISPATCH
;
448 var
[0].pdispVal
= dispatchex
;
449 dispparams
.rgvarg
= var
;
450 dispparams
.rgdispidNamedArgs
= &disp_this
;
451 dispparams
.cNamedArgs
= 1;
452 dispparams
.cArgs
= 1;
454 hr
= dispatchex
->InvokeEx(
455 DISPID_VALUE
, LOCALE_USER_DEFAULT
,
456 DISPATCH_METHOD
, &dispparams
,
460 // Fallback on IDispatch if needed.
463 dispparams
.rgvarg
= var
;
464 dispparams
.cArgs
= 0;
466 hr
= dispatch
->Invoke(
467 DISPID_VALUE
, IID_NULL
, LOCALE_USER_DEFAULT
,
468 DISPATCH_METHOD
, &dispparams
,
469 NULL
, NULL
, &arg_err
);
475 HttpRequest::ReadyState
GearsHttpRequest::GetState() {
476 HttpRequest::ReadyState state
= HttpRequest::UNINITIALIZED
;
478 request_
->GetReadyState(&state
);
484 void GearsHttpRequest::CreateRequest() {
486 request_
= HttpRequest::Create();
487 request_
->SetOnReadyStateChange(this);
488 request_
->SetCachingBehavior(HttpRequest::USE_ALL_CACHES
);
489 request_
->SetRedirectBehavior(HttpRequest::FOLLOW_WITHIN_ORIGIN
);
493 void GearsHttpRequest::ReleaseRequest() {
495 request_
->SetOnReadyStateChange(NULL
);
496 request_
->ReleaseReference();
498 response_text_
.reset(NULL
);
499 response_blob_
= NULL
;
504 //------------------------------------------------------------------------------
505 // This helper does several things:
506 // - resolve relative urls based on the page location, the 'url' may also
507 // be an absolute url to start with, if so this step does not modify it
508 // - normalizes the resulting absolute url, ie. removes path navigation
509 // - removes the fragment part of the url, ie. truncates at the '#' character
510 // - ensures the the resulting url is from the same-origin
511 // - ensures the requested url is HTTP or HTTPS
512 //------------------------------------------------------------------------------
513 bool GearsHttpRequest::ResolveUrl(const char16
*url
,
514 std::string16
*resolved_url
,
515 std::string16
*exception_message
) {
516 assert(url
&& resolved_url
&& exception_message
);
517 if (!ResolveAndNormalize(EnvPageLocationUrl().c_str(), url
, resolved_url
)) {
518 *exception_message
= STRING16(L
"Failed to resolve URL.");
522 SecurityOrigin url_origin
;
523 if (!url_origin
.InitFromUrl(resolved_url
->c_str()) ||
524 !url_origin
.IsSameOrigin(EnvPageSecurityOrigin())) {
525 *exception_message
= STRING16(L
"URL is not from the same origin.");
529 if (!HttpRequest::IsSchemeSupported(url_origin
.scheme().c_str())) {
530 *exception_message
= STRING16(L
"URL scheme '");
531 *exception_message
+= url_origin
.scheme();
532 *exception_message
+= STRING16(L
"' is not supported.");