[Author: kevinww]
[google-gears.git] / gears / httprequest / ie / httprequest_ie.cc
blobc7a982f3b98e9fa912bec86459720c5a4ef0135d
1 // Copyright 2006, Google Inc.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are met:
5 //
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.
26 #include <assert.h>
27 #include <dispex.h>
28 #include <vector>
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"
41 // Error messages
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() {
57 abort();
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;
67 } else {
68 RETURN_EXCEPTION(STRING16(
69 L"The onmesonreadystatechangesage callback must be a function."));
72 onreadystatechangehandler_ = handler_dispatch;
73 RETURN_NORMAL();
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
85 RETURN_NORMAL();
89 STDMETHODIMP GearsHttpRequest::get_readyState(
90 /* [retval][out] */ int *state) {
91 if (!state) return E_POINTER;
92 *state = GetState();
93 RETURN_NORMAL();
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;
103 if (IsComplete()) {
104 ReleaseRequest();
106 if (!IsUninitialized()) {
107 RETURN_EXCEPTION(kAlreadyOpenError);
110 if (!method[0]) {
111 RETURN_EXCEPTION(STRING16(L"The method parameter must be a string."));
113 if (!url[0]) {
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());
122 CreateRequest();
124 if (unload_monitor_ == NULL) {
125 unload_monitor_.reset(new JsEventMonitor(GetJsRunner(), JSEVENT_UNLOAD,
126 this));
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);
136 RETURN_NORMAL();
140 void GearsHttpRequest::HandleEvent(JsEventType event_type) {
141 assert(event_type == JSEVENT_UNLOAD);
142 onreadystatechangehandler_.Release();
143 unload_monitor_.reset(NULL);
144 abort();
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"),
156 STRING16(L"Date"),
157 STRING16(L"Expect"),
158 STRING16(L"Host"),
159 STRING16(L"Keep-Alive"),
160 STRING16(L"Referer"),
161 STRING16(L"TE"),
162 STRING16(L"Trailer"),
163 STRING16(L"Transfer-Encoding"),
164 STRING16(L"Upgrade"),
165 STRING16(L"Via") };
166 for (int i = 0; i < static_cast<int>(ARRAYSIZE(kDisallowedHeaders)); ++i) {
167 if (StringCompareIgnoreCase(header, kDisallowedHeaders[i]) == 0)
168 return true;
170 return false;
174 STDMETHODIMP GearsHttpRequest::setRequestHeader(
175 /* [in] */ const BSTR name,
176 /* [in] */ const BSTR value) {
177 if (!name) return E_POINTER;
178 if (!IsOpen()) {
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;
190 RETURN_NORMAL();
194 STDMETHODIMP GearsHttpRequest::send(
195 /* [optional][in] */ const VARIANT *data) {
196 if (!IsOpen())
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_;
209 bool ok = false;
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);
216 } else {
217 ok = request_->Send();
220 if (!ok) {
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);
235 RETURN_NORMAL();
239 STDMETHODIMP GearsHttpRequest::abort() {
240 if (request_) {
241 request_->SetOnReadyStateChange(NULL);
242 request_->Abort();
243 ReleaseRequest();
245 RETURN_NORMAL();
249 bool GearsHttpRequest::IsValidResponse() {
250 assert(IsInteractive() || IsComplete());
251 int status_code = -1;
252 if (!request_->GetStatus(&status_code))
253 return false;
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
266 RETURN_NORMAL();
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();
275 RETURN_NORMAL();
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;
288 RETURN_NORMAL();
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();
296 RETURN_NORMAL();
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()) {
307 *body = NULL;
308 RETURN_NORMAL();
311 if (!IsComplete()) {
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();
317 RETURN_NORMAL();
320 if (response_text_ == NULL) {
321 // Cache the body
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();
332 RETURN_NORMAL();
336 #ifdef OFFICIAL_BUILD
337 // Blob support is not ready for prime time yet
338 #else
339 STDMETHODIMP GearsHttpRequest::get_responseBlob(
340 /* [retval][out] */ IUnknown **blob) {
341 if (!IsComplete())
342 RETURN_EXCEPTION(STRING16(L"responseBlob is not supported before request "
343 L"is complete"));
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);
349 if (FAILED(hr)) {
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();
361 if (body) {
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_;
374 (*blob)->AddRef();
375 RETURN_NORMAL();
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);
393 RETURN_NORMAL();
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();
411 RETURN_NORMAL();
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_) {
425 HRESULT hr = 0;
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_;
431 if (IsComplete()) {
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;
442 if (dispatchex) {
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;
446 VARIANT var[1];
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,
457 NULL, NULL, NULL);
459 } else {
460 // Fallback on IDispatch if needed.
461 UINT arg_err = 0;
462 VARIANT var[1];
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;
477 if (request_) {
478 request_->GetReadyState(&state);
480 return state;
484 void GearsHttpRequest::CreateRequest() {
485 ReleaseRequest();
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() {
494 if (request_) {
495 request_->SetOnReadyStateChange(NULL);
496 request_->ReleaseReference();
497 request_ = NULL;
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.");
519 return false;
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.");
526 return false;
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.");
533 return false;
536 return true;