// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // Tests PPB_URLRequestInfo interface. #include "ppapi/tests/test_url_request.h" #include <string.h> #include <string> #include "ppapi/c/ppb_file_io.h" #include "ppapi/cpp/completion_callback.h" #include "ppapi/cpp/file_io.h" #include "ppapi/cpp/file_ref.h" #include "ppapi/cpp/file_system.h" #include "ppapi/cpp/instance.h" #include "ppapi/cpp/var.h" #include "ppapi/tests/test_utils.h" #include "ppapi/tests/testing_instance.h" REGISTER_TEST_CASE(URLRequest); namespace { // TODO(polina): move these to test_case.h/cc since other NaCl tests use them? const PP_Resource kInvalidResource = 0; const PP_Instance kInvalidInstance = 0; // These should not exist. // The bottom 2 bits are used to differentiate between different id types. // 00 - module, 01 - instance, 10 - resource, 11 - var. const PP_Instance kNotAnInstance = 0xFFFFF0; const PP_Resource kNotAResource = 0xAAAAA0; } TestURLRequest::TestURLRequest(TestingInstance* instance) : TestCase(instance), ppb_url_request_interface_(NULL), ppb_url_loader_interface_(NULL), ppb_url_response_interface_(NULL), ppb_core_interface_(NULL), ppb_var_interface_(NULL), url_loader_(kInvalidResource) { } bool TestURLRequest::Init() { ppb_url_request_interface_ = static_cast<const PPB_URLRequestInfo*>( pp::Module::Get()->GetBrowserInterface(PPB_URLREQUESTINFO_INTERFACE)); ppb_url_loader_interface_ = static_cast<const PPB_URLLoader*>( pp::Module::Get()->GetBrowserInterface(PPB_URLLOADER_INTERFACE)); ppb_url_response_interface_ = static_cast<const PPB_URLResponseInfo*>( pp::Module::Get()->GetBrowserInterface(PPB_URLRESPONSEINFO_INTERFACE)); ppb_core_interface_ = static_cast<const PPB_Core*>( pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE)); ppb_var_interface_ = static_cast<const PPB_Var*>( pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE)); if (!ppb_url_request_interface_) instance_->AppendError("PPB_URLRequestInfo interface not available"); if (!ppb_url_response_interface_) instance_->AppendError("PPB_URLResponseInfo interface not available"); if (!ppb_core_interface_) instance_->AppendError("PPB_Core interface not available"); if (!ppb_var_interface_) instance_->AppendError("PPB_Var interface not available"); if (!ppb_url_loader_interface_) { instance_->AppendError("PPB_URLLoader interface not available"); } else { url_loader_ = ppb_url_loader_interface_->Create(instance_->pp_instance()); if (url_loader_ == kInvalidResource) instance_->AppendError("Failed to create URLLoader"); } return EnsureRunningOverHTTP(); } void TestURLRequest::RunTests(const std::string& filter) { RUN_TEST(CreateAndIsURLRequestInfo, filter); RUN_TEST(SetProperty, filter); RUN_TEST(AppendDataToBody, filter); RUN_TEST(AppendFileToBody, filter); RUN_TEST(Stress, filter); } PP_Var TestURLRequest::PP_MakeString(const char* s) { return ppb_var_interface_->VarFromUtf8(s, strlen(s)); } // Tests // PP_Resource Create(PP_Instance instance) // PP_Bool IsURLRequestInfo(PP_Resource resource) std::string TestURLRequest::TestCreateAndIsURLRequestInfo() { // Create: Invalid / non-existent instance -> invalid resource. ASSERT_EQ(ppb_url_request_interface_->Create(kInvalidInstance), kInvalidResource); ASSERT_EQ(ppb_url_request_interface_->Create(kNotAnInstance), kInvalidResource); // Create: Valid instance -> valid resource. PP_Resource url_request = ppb_url_request_interface_->Create( instance_->pp_instance()); ASSERT_NE(url_request, kInvalidResource); // IsURLRequestInfo: // Invalid / non-existent / non-URLRequestInfo resource -> false. ASSERT_NE(PP_TRUE, ppb_url_request_interface_->IsURLRequestInfo(kInvalidResource)); ASSERT_NE(PP_TRUE, ppb_url_request_interface_->IsURLRequestInfo(kNotAResource)); ASSERT_NE(PP_TRUE, ppb_url_request_interface_->IsURLRequestInfo(url_loader_)); // IsURLRequestInfo: Current URLRequestInfo resource -> true. std::string error; if (PP_FALSE == ppb_url_request_interface_->IsURLRequestInfo(url_request)) error = "IsURLRequestInfo() failed with a current URLRequestInfo resource"; // IsURLRequestInfo: Released URLRequestInfo resource -> false. ppb_core_interface_->ReleaseResource(url_request); ASSERT_NE(PP_TRUE, ppb_url_request_interface_->IsURLRequestInfo(url_request)); return error; // == PASS() if empty. } // Tests // PP_Bool SetProperty(PP_Resource request, // PP_URLRequestProperty property, // struct PP_Var value); std::string TestURLRequest::TestSetProperty() { struct PropertyTestData { PropertyTestData(PP_URLRequestProperty prop, const std::string& name, PP_Var value, PP_Bool expected) : property(prop), property_name(name), var(value), expected_value(expected) { // var has ref count of 1 on creation. } PP_URLRequestProperty property; std::string property_name; PP_Var var; // Instance owner is responsible for releasing this var. PP_Bool expected_value; }; // All bool properties should accept PP_TRUE and PP_FALSE, while rejecting // all other variable types. #define TEST_BOOL(_name) \ PropertyTestData(ID_STR(_name), PP_MakeBool(PP_TRUE), PP_TRUE), \ PropertyTestData(ID_STR(_name), PP_MakeBool(PP_FALSE), PP_TRUE), \ PropertyTestData(ID_STR(_name), PP_MakeUndefined(), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeNull(), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeInt32(0), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeDouble(0.0), PP_FALSE) // These property types are always invalid for string properties. #define TEST_STRING_INVALID(_name) \ PropertyTestData(ID_STR(_name), PP_MakeNull(), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeBool(PP_FALSE), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeInt32(0), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeDouble(0.0), PP_FALSE) #define TEST_INT_INVALID(_name) \ PropertyTestData(ID_STR(_name), PP_MakeUndefined(), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeNull(), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeBool(PP_FALSE), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeString("notint"), PP_FALSE), \ PropertyTestData(ID_STR(_name), PP_MakeDouble(0.0), PP_FALSE) // SetProperty accepts plenty of invalid values (malformed urls, negative // thresholds, etc). Error checking is delayed until request opening (aka url // loading). #define ID_STR(arg) arg, #arg PropertyTestData test_data[] = { TEST_BOOL(PP_URLREQUESTPROPERTY_STREAMTOFILE), TEST_BOOL(PP_URLREQUESTPROPERTY_FOLLOWREDIRECTS), TEST_BOOL(PP_URLREQUESTPROPERTY_RECORDDOWNLOADPROGRESS), TEST_BOOL(PP_URLREQUESTPROPERTY_RECORDUPLOADPROGRESS), TEST_BOOL(PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS), TEST_BOOL(PP_URLREQUESTPROPERTY_ALLOWCREDENTIALS), TEST_STRING_INVALID(PP_URLREQUESTPROPERTY_URL), TEST_STRING_INVALID(PP_URLREQUESTPROPERTY_METHOD), TEST_STRING_INVALID(PP_URLREQUESTPROPERTY_HEADERS), TEST_STRING_INVALID(PP_URLREQUESTPROPERTY_CUSTOMREFERRERURL), TEST_STRING_INVALID(PP_URLREQUESTPROPERTY_CUSTOMCONTENTTRANSFERENCODING), TEST_STRING_INVALID(PP_URLREQUESTPROPERTY_CUSTOMUSERAGENT), TEST_INT_INVALID(PP_URLREQUESTPROPERTY_PREFETCHBUFFERUPPERTHRESHOLD), TEST_INT_INVALID(PP_URLREQUESTPROPERTY_PREFETCHBUFFERLOWERTHRESHOLD), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_URL), PP_MakeString("http://www.google.com"), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_URL), PP_MakeString("foo.jpg"), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_METHOD), PP_MakeString("GET"), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_METHOD), PP_MakeString("POST"), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_HEADERS), PP_MakeString("Accept: text/plain"), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_HEADERS), PP_MakeString(""), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_CUSTOMREFERRERURL), PP_MakeString("http://www.google.com"), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_CUSTOMREFERRERURL), PP_MakeString(""), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_CUSTOMREFERRERURL), PP_MakeUndefined(), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_CUSTOMCONTENTTRANSFERENCODING), PP_MakeString("base64"), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_CUSTOMCONTENTTRANSFERENCODING), PP_MakeString(""), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_CUSTOMCONTENTTRANSFERENCODING), PP_MakeUndefined(), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_CUSTOMUSERAGENT), PP_MakeString("My Crazy Plugin"), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_CUSTOMUSERAGENT), PP_MakeString(""), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_CUSTOMUSERAGENT), PP_MakeUndefined(), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_URL), PP_MakeUndefined(), PP_FALSE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_METHOD), PP_MakeUndefined(), PP_FALSE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_HEADERS), PP_MakeString("Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA=="), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_HEADERS), PP_MakeString("Accept-Encoding: *\n" "Accept-Charset: iso-8859-5, unicode-1-1;q=0.8"), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_PREFETCHBUFFERUPPERTHRESHOLD), PP_MakeInt32(0), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_PREFETCHBUFFERUPPERTHRESHOLD), PP_MakeInt32(100), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_PREFETCHBUFFERLOWERTHRESHOLD), PP_MakeInt32(0), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_PREFETCHBUFFERLOWERTHRESHOLD), PP_MakeInt32(100), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_URL), PP_MakeString("::::::::::::"), PP_TRUE), PropertyTestData(ID_STR(PP_URLREQUESTPROPERTY_METHOD), PP_MakeString("INVALID"), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_CUSTOMCONTENTTRANSFERENCODING), PP_MakeString("invalid"), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_PREFETCHBUFFERUPPERTHRESHOLD), PP_MakeInt32(-100), PP_TRUE), PropertyTestData( ID_STR(PP_URLREQUESTPROPERTY_PREFETCHBUFFERLOWERTHRESHOLD), PP_MakeInt32(-100), PP_TRUE), }; std::string error; PP_Resource url_request = ppb_url_request_interface_->Create( instance_->pp_instance()); if (url_request == kInvalidResource) error = "Failed to create a URLRequestInfo"; // Loop over all test data even if we encountered an error to release vars. for (size_t i = 0; i < sizeof(test_data) / sizeof(test_data[0]); ++i) { if (error.empty() && test_data[i].expected_value != ppb_url_request_interface_->SetProperty(url_request, test_data[i].property, test_data[i].var)) { pp::Var var(pp::Var::DontManage(), test_data[i].var); error = std::string("Setting property ") + test_data[i].property_name + " to " + var.DebugString() + " did not return " + (test_data[i].expected_value ? "True" : "False"); error = test_data[i].property_name; } ppb_var_interface_->Release(test_data[i].var); } ppb_core_interface_->ReleaseResource(url_request); return error; // == PASS() if empty. } std::string TestURLRequest::LoadAndCompareBody( PP_Resource url_request, const std::string& expected_body) { TestCompletionCallback callback(instance_->pp_instance(), PP_REQUIRED); callback.WaitForResult(ppb_url_loader_interface_->Open( url_loader_, url_request, callback.GetCallback().pp_completion_callback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); std::string error; PP_Resource url_response = ppb_url_loader_interface_->GetResponseInfo(url_loader_); if (url_response == kInvalidResource) { error = "PPB_URLLoader::GetResponseInfo() returned invalid resource"; } else { PP_Var status = ppb_url_response_interface_->GetProperty( url_response, PP_URLRESPONSEPROPERTY_STATUSCODE); if (status.type != PP_VARTYPE_INT32 && status.value.as_int != 200) error = ReportError("PPB_URLLoader::Open() status", status.value.as_int); std::string actual_body; for (; error.empty();) { // Read the entire body in this loop. const size_t kBufferSize = 32; char buf[kBufferSize]; callback.WaitForResult(ppb_url_loader_interface_->ReadResponseBody( url_loader_, buf, kBufferSize, callback.GetCallback().pp_completion_callback())); if (callback.failed()) error.assign(callback.errors()); else if (callback.result() < PP_OK) error.assign(ReportError("PPB_URLLoader::ReadResponseBody()", callback.result())); if (callback.result() <= PP_OK || callback.failed()) break; actual_body.append(buf, callback.result()); } if (actual_body != expected_body) error = "PPB_URLLoader::ReadResponseBody() read unexpected response."; } ppb_core_interface_->ReleaseResource(url_response); ppb_url_loader_interface_->Close(url_loader_); return error; } // Tests // PP_Bool AppendDataToBody( // PP_Resource request, const void* data, uint32_t len); std::string TestURLRequest::TestAppendDataToBody() { PP_Resource url_request = ppb_url_request_interface_->Create( instance_->pp_instance()); ASSERT_NE(url_request, kInvalidResource); std::string postdata("sample postdata"); PP_Var post_string_var = PP_MakeString("POST"); PP_Var echo_string_var = PP_MakeString("/echo"); // NULL pointer causes a crash. In general PPAPI implementation does not // test for NULL because they are just a special case of bad pointers that // are not detectable if set to point to an object that does not exist. // Invalid resource should fail. ASSERT_EQ(PP_FALSE, ppb_url_request_interface_->AppendDataToBody( kInvalidResource, postdata.data(), postdata.length())); // Append data and POST to echoing web server. ASSERT_EQ(PP_TRUE, ppb_url_request_interface_->SetProperty( url_request, PP_URLREQUESTPROPERTY_METHOD, post_string_var)); ASSERT_EQ(PP_TRUE, ppb_url_request_interface_->SetProperty( url_request, PP_URLREQUESTPROPERTY_URL, echo_string_var)); // Append data to body and verify the body is what we expect. ASSERT_EQ(PP_TRUE, ppb_url_request_interface_->AppendDataToBody( url_request, postdata.data(), postdata.length())); std::string error = LoadAndCompareBody(url_request, postdata); ppb_var_interface_->Release(post_string_var); ppb_var_interface_->Release(echo_string_var); ppb_core_interface_->ReleaseResource(url_request); return error; // == PASS() if empty. } std::string TestURLRequest::TestAppendFileToBody() { PP_Resource url_request = ppb_url_request_interface_->Create( instance_->pp_instance()); ASSERT_NE(url_request, kInvalidResource); TestCompletionCallback callback(instance_->pp_instance(), callback_type()); pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); callback.WaitForResult(file_system.Open(1024, callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); pp::FileRef ref(file_system, "/test_file"); pp::FileIO io(instance_); callback.WaitForResult(io.Open(ref, PP_FILEOPENFLAG_CREATE | PP_FILEOPENFLAG_WRITE, callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(PP_OK, callback.result()); std::string append_data = "hello\n"; callback.WaitForResult(io.Write(0, append_data.c_str(), append_data.size(), callback.GetCallback())); CHECK_CALLBACK_BEHAVIOR(callback); ASSERT_EQ(static_cast<int32_t>(append_data.size()), callback.result()); PP_Var post_string_var = PP_MakeString("POST"); PP_Var echo_string_var = PP_MakeString("/echo"); // NULL pointer causes a crash. In general PPAPI implementation does not // test for NULL because they are just a special case of bad pointers that // are not detectable if set to point to an object that does not exist. // Invalid resource should fail. ASSERT_EQ(PP_FALSE, ppb_url_request_interface_->AppendFileToBody( kInvalidResource, ref.pp_resource(), 0, -1, 0)); // Append data and POST to echoing web server. ASSERT_EQ(PP_TRUE, ppb_url_request_interface_->SetProperty( url_request, PP_URLREQUESTPROPERTY_METHOD, post_string_var)); ASSERT_EQ(PP_TRUE, ppb_url_request_interface_->SetProperty( url_request, PP_URLREQUESTPROPERTY_URL, echo_string_var)); // Append file to body and verify the body is what we expect. ASSERT_EQ(PP_TRUE, ppb_url_request_interface_->AppendFileToBody( url_request, ref.pp_resource(), 0, -1, 0)); std::string error = LoadAndCompareBody(url_request, append_data); ppb_var_interface_->Release(post_string_var); ppb_var_interface_->Release(echo_string_var); ppb_core_interface_->ReleaseResource(url_request); return error; // == PASS() if empty. } // Allocates and manipulates a large number of resources. std::string TestURLRequest::TestStress() { const int kManyResources = 500; PP_Resource url_request_info[kManyResources]; std::string error; int num_created = kManyResources; for (int i = 0; i < kManyResources; i++) { url_request_info[i] = ppb_url_request_interface_->Create( instance_->pp_instance()); if (url_request_info[i] == kInvalidResource) { error = "Create() failed"; } else if (PP_FALSE == ppb_url_request_interface_->IsURLRequestInfo( url_request_info[i])) { error = "IsURLRequestInfo() failed"; } else if (PP_FALSE == ppb_url_request_interface_->SetProperty( url_request_info[i], PP_URLREQUESTPROPERTY_STREAMTOFILE, PP_MakeBool(PP_FALSE))) { error = "SetProperty() failed"; } if (!error.empty()) { num_created = i + 1; break; } } for (int i = 0; i < num_created; i++) { ppb_core_interface_->ReleaseResource(url_request_info[i]); if (PP_TRUE == ppb_url_request_interface_->IsURLRequestInfo(url_request_info[i])) error = "IsURLREquestInfo() succeeded after release"; } return error; // == PASS() if empty. }