| // 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. | |
| #include "ppapi/tests/test_url_loader.h" | |
| #include <stddef.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <string> | |
| #include "ppapi/c/pp_errors.h" | |
| #include "ppapi/c/ppb_file_io.h" | |
| #include "ppapi/c/ppb_url_loader.h" | |
| #include "ppapi/c/trusted/ppb_url_loader_trusted.h" | |
| #include "ppapi/cpp/dev/url_util_dev.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/module.h" | |
| #include "ppapi/cpp/private/file_io_private.h" | |
| #include "ppapi/cpp/url_loader.h" | |
| #include "ppapi/cpp/url_request_info.h" | |
| #include "ppapi/cpp/url_response_info.h" | |
| #include "ppapi/tests/test_utils.h" | |
| #include "ppapi/tests/testing_instance.h" | |
| REGISTER_TEST_CASE(URLLoader); | |
| namespace { | |
| int32_t WriteEntireBuffer(PP_Instance instance, | |
| pp::FileIO* file_io, | |
| int32_t offset, | |
| const std::string& data, | |
| CallbackType callback_type) { | |
| TestCompletionCallback callback(instance, callback_type); | |
| int32_t write_offset = offset; | |
| const char* buf = data.c_str(); | |
| int32_t size = static_cast<int32_t>(data.size()); | |
| while (write_offset < offset + size) { | |
| callback.WaitForResult(file_io->Write(write_offset, | |
| &buf[write_offset - offset], | |
| size - write_offset + offset, | |
| callback.GetCallback())); | |
| if (callback.result() < 0) | |
| return callback.result(); | |
| if (callback.result() == 0) | |
| return PP_ERROR_FAILED; | |
| write_offset += callback.result(); | |
| } | |
| return PP_OK; | |
| } | |
| } // namespace | |
| TestURLLoader::TestURLLoader(TestingInstance* instance) | |
| : TestCase(instance), | |
| file_io_private_interface_(NULL), | |
| url_loader_trusted_interface_(NULL) { | |
| } | |
| bool TestURLLoader::Init() { | |
| if (!CheckTestingInterface()) { | |
| instance_->AppendError("Testing interface not available"); | |
| return false; | |
| } | |
| const PPB_FileIO* file_io_interface = static_cast<const PPB_FileIO*>( | |
| pp::Module::Get()->GetBrowserInterface(PPB_FILEIO_INTERFACE)); | |
| if (!file_io_interface) | |
| instance_->AppendError("FileIO interface not available"); | |
| file_io_private_interface_ = static_cast<const PPB_FileIO_Private*>( | |
| pp::Module::Get()->GetBrowserInterface(PPB_FILEIO_PRIVATE_INTERFACE)); | |
| if (!file_io_private_interface_) | |
| instance_->AppendError("FileIO_Private interface not available"); | |
| url_loader_trusted_interface_ = static_cast<const PPB_URLLoaderTrusted*>( | |
| pp::Module::Get()->GetBrowserInterface(PPB_URLLOADERTRUSTED_INTERFACE)); | |
| if (!testing_interface_->IsOutOfProcess()) { | |
| // Trusted interfaces are not supported under NaCl. | |
| #if !(defined __native_client__) | |
| if (!url_loader_trusted_interface_) | |
| instance_->AppendError("URLLoaderTrusted interface not available"); | |
| #else | |
| if (url_loader_trusted_interface_) | |
| instance_->AppendError("URLLoaderTrusted interface is supported by NaCl"); | |
| #endif | |
| } | |
| return EnsureRunningOverHTTP(); | |
| } | |
| /* | |
| * The test order is important here, as running tests out of order may cause | |
| * test timeout. | |
| * | |
| * Here is the environment: | |
| * | |
| * 1. TestServer.py only accepts one open connection at the time. | |
| * 2. HTTP socket pool keeps sockets open for several seconds after last use | |
| * (hoping that there will be another request that could reuse the connection). | |
| * 3. HTTP socket pool is separated by host/port and privacy mode (which is | |
| * based on cookies set/get permissions). So, connections to 127.0.0.1, | |
| * localhost and localhost in privacy mode cannot reuse existing socket and will | |
| * try to open another connection. | |
| * | |
| * Here is the problem: | |
| * | |
| * Original test order was repeatedly accessing 127.0.0.1, localhost and | |
| * localhost in privacy mode, causing new sockets to open and try to connect to | |
| * testserver, which they couldn't until previous connection is closed by socket | |
| * pool idle socket timeout (10 seconds). | |
| * | |
| * Because of this the test run was taking around 45 seconds, and test was | |
| * reported as 'timed out' by trybot. | |
| * | |
| * Re-ordering of tests provides more sequential access to 127.0.0.1, localhost | |
| * and localhost in privacy mode. It decreases the number of times when socket | |
| * pool doesn't have existing connection to host and has to wait, therefore | |
| * reducing total test time and ensuring its completion under 30 seconds. | |
| */ | |
| void TestURLLoader::RunTests(const std::string& filter) { | |
| // These tests connect to 127.0.0.1: | |
| RUN_CALLBACK_TEST(TestURLLoader, BasicGET, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, BasicPOST, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, BasicFilePOST, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, BasicFileRangePOST, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, CompoundBodyPOST, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, EmptyDataPOST, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, BinaryDataPOST, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, CustomRequestHeader, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, FailsBogusContentLength, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, StreamToFile, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, UntrustedJavascriptURLRestriction, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, TrustedJavascriptURLRestriction, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, UntrustedHttpRequests, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, TrustedHttpRequests, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, FollowURLRedirect, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, AuditURLRedirect, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, AbortCalls, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, UntendedLoad, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, PrefetchBufferThreshold, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, XRequestedWithHeader, filter); | |
| // These tests connect to localhost with privacy mode enabled: | |
| RUN_CALLBACK_TEST(TestURLLoader, UntrustedSameOriginRestriction, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, UntrustedCrossOriginRequest, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, UntrustedCorbEligibleRequest, filter); | |
| // These tests connect to localhost with privacy mode disabled: | |
| RUN_CALLBACK_TEST(TestURLLoader, TrustedSameOriginRestriction, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, TrustedCrossOriginRequest, filter); | |
| RUN_CALLBACK_TEST(TestURLLoader, TrustedCorbEligibleRequest, filter); | |
| } | |
| std::string TestURLLoader::ReadEntireFile(pp::FileIO* file_io, | |
| std::string* data) { | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| char buf[256]; | |
| int64_t offset = 0; | |
| for (;;) { | |
| callback.WaitForResult(file_io->Read(offset, buf, sizeof(buf), | |
| callback.GetCallback())); | |
| if (callback.result() < 0) | |
| return ReportError("FileIO::Read", callback.result()); | |
| if (callback.result() == 0) | |
| break; | |
| offset += callback.result(); | |
| data->append(buf, callback.result()); | |
| } | |
| PASS(); | |
| } | |
| std::string TestURLLoader::ReadEntireResponseBody(pp::URLLoader* loader, | |
| std::string* body) { | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| char buf[2]; // Small so that multiple reads are needed. | |
| for (;;) { | |
| callback.WaitForResult( | |
| loader->ReadResponseBody(buf, sizeof(buf), callback.GetCallback())); | |
| if (callback.result() < 0) | |
| return ReportError("URLLoader::ReadResponseBody", callback.result()); | |
| if (callback.result() == 0) | |
| break; | |
| body->append(buf, callback.result()); | |
| } | |
| PASS(); | |
| } | |
| std::string TestURLLoader::LoadAndCompareBody( | |
| const pp::URLRequestInfo& request, | |
| const std::string& expected_body) { | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| pp::URLLoader loader(instance_); | |
| callback.WaitForResult(loader.Open(request, callback.GetCallback())); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| ASSERT_EQ(PP_OK, callback.result()); | |
| pp::URLResponseInfo response_info(loader.GetResponseInfo()); | |
| if (response_info.is_null()) | |
| return "URLLoader::GetResponseInfo returned null"; | |
| int32_t status_code = response_info.GetStatusCode(); | |
| if (status_code != 200) | |
| return "Unexpected HTTP status code"; | |
| std::string body; | |
| std::string error = ReadEntireResponseBody(&loader, &body); | |
| if (!error.empty()) | |
| return error; | |
| if (body.size() != expected_body.size()) | |
| return "URLLoader::ReadResponseBody returned unexpected content length"; | |
| if (body != expected_body) | |
| return "URLLoader::ReadResponseBody returned unexpected content"; | |
| PASS(); | |
| } | |
| int32_t TestURLLoader::OpenFileSystem(pp::FileSystem* file_system, | |
| std::string* message) { | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| callback.WaitForResult(file_system->Open(1024, callback.GetCallback())); | |
| if (callback.failed()) { | |
| message->assign(callback.errors()); | |
| return callback.result(); | |
| } | |
| if (callback.result() != PP_OK) { | |
| message->assign("FileSystem::Open"); | |
| return callback.result(); | |
| } | |
| return callback.result(); | |
| } | |
| int32_t TestURLLoader::PrepareFileForPost( | |
| const pp::FileRef& file_ref, | |
| const std::string& data, | |
| std::string* message) { | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| pp::FileIO file_io(instance_); | |
| callback.WaitForResult(file_io.Open(file_ref, | |
| PP_FILEOPENFLAG_CREATE | | |
| PP_FILEOPENFLAG_TRUNCATE | | |
| PP_FILEOPENFLAG_WRITE, | |
| callback.GetCallback())); | |
| if (callback.failed()) { | |
| message->assign(callback.errors()); | |
| return callback.result(); | |
| } | |
| if (callback.result() != PP_OK) { | |
| message->assign("FileIO::Open failed."); | |
| return callback.result(); | |
| } | |
| int32_t rv = WriteEntireBuffer(instance_->pp_instance(), &file_io, 0, data, | |
| callback_type()); | |
| if (rv != PP_OK) { | |
| message->assign("FileIO::Write failed."); | |
| return rv; | |
| } | |
| return rv; | |
| } | |
| std::string TestURLLoader::GetReachableAbsoluteURL( | |
| const std::string& file_name) { | |
| // Get the absolute page URL and replace the test case file name | |
| // with the given one. | |
| pp::Var document_url( | |
| pp::PASS_REF, | |
| testing_interface_->GetDocumentURL(instance_->pp_instance(), | |
| NULL)); | |
| std::string url(document_url.AsString()); | |
| std::string old_name("test_case.html"); | |
| size_t index = url.find(old_name); | |
| ASSERT_NE(index, std::string::npos); | |
| url.replace(index, old_name.length(), file_name); | |
| return url; | |
| } | |
| std::string TestURLLoader::GetReachableCrossOriginURL( | |
| const std::string& file_name) { | |
| // Get an absolute URL and use it to construct a URL that will be | |
| // considered cross-origin by the CORS access control code, and yet be | |
| // reachable by the test server. | |
| std::string url = GetReachableAbsoluteURL(file_name); | |
| // Replace '127.0.0.1' with 'localhost'. | |
| std::string host("127.0.0.1"); | |
| size_t index = url.find(host); | |
| ASSERT_NE(index, std::string::npos); | |
| url.replace(index, host.length(), "localhost"); | |
| return url; | |
| } | |
| int32_t TestURLLoader::OpenUntrusted(const std::string& method, | |
| const std::string& header) { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod(method); | |
| request.SetHeaders(header); | |
| return OpenUntrusted(request, NULL); | |
| } | |
| int32_t TestURLLoader::OpenTrusted(const std::string& method, | |
| const std::string& header) { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod(method); | |
| request.SetHeaders(header); | |
| return OpenTrusted(request, NULL); | |
| } | |
| int32_t TestURLLoader::OpenUntrusted(const pp::URLRequestInfo& request, | |
| std::string* response_body) { | |
| return Open(request, false, response_body); | |
| } | |
| int32_t TestURLLoader::OpenTrusted(const pp::URLRequestInfo& request, | |
| std::string* response_body) { | |
| return Open(request, true, response_body); | |
| } | |
| int32_t TestURLLoader::Open(const pp::URLRequestInfo& request, | |
| bool trusted, | |
| std::string* response_body) { | |
| pp::URLLoader loader(instance_); | |
| if (trusted) | |
| url_loader_trusted_interface_->GrantUniversalAccess(loader.pp_resource()); | |
| return OpenURLRequest(instance_->pp_instance(), &loader, request, | |
| callback_type(), response_body); | |
| } | |
| std::string TestURLLoader::TestBasicGET() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("test_url_loader_data/hello.txt"); | |
| return LoadAndCompareBody(request, "hello\n"); | |
| } | |
| std::string TestURLLoader::TestBasicPOST() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod("POST"); | |
| std::string postdata("postdata"); | |
| request.AppendDataToBody(postdata.data(), | |
| static_cast<uint32_t>(postdata.length())); | |
| return LoadAndCompareBody(request, postdata); | |
| } | |
| std::string TestURLLoader::TestBasicFilePOST() { | |
| std::string message; | |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); | |
| int32_t rv = OpenFileSystem(&file_system, &message); | |
| if (rv != PP_OK) | |
| return ReportError(message.c_str(), rv); | |
| pp::FileRef file_ref(file_system, "/file_post_test"); | |
| std::string postdata("postdata"); | |
| rv = PrepareFileForPost(file_ref, postdata, &message); | |
| if (rv != PP_OK) | |
| return ReportError(message.c_str(), rv); | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod("POST"); | |
| request.AppendFileToBody(file_ref, 0); | |
| return LoadAndCompareBody(request, postdata); | |
| } | |
| std::string TestURLLoader::TestBasicFileRangePOST() { | |
| std::string message; | |
| pp::FileSystem file_system(instance_, PP_FILESYSTEMTYPE_LOCALTEMPORARY); | |
| int32_t rv = OpenFileSystem(&file_system, &message); | |
| if (rv != PP_OK) | |
| return ReportError(message.c_str(), rv); | |
| pp::FileRef file_ref(file_system, "/file_range_post_test"); | |
| std::string postdata("postdatapostdata"); | |
| rv = PrepareFileForPost(file_ref, postdata, &message); | |
| if (rv != PP_OK) | |
| return ReportError(message.c_str(), rv); | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod("POST"); | |
| request.AppendFileRangeToBody(file_ref, 4, 12, 0); | |
| return LoadAndCompareBody(request, postdata.substr(4, 12)); | |
| } | |
| std::string TestURLLoader::TestCompoundBodyPOST() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod("POST"); | |
| std::string postdata1("post"); | |
| request.AppendDataToBody(postdata1.data(), | |
| static_cast<uint32_t>(postdata1.length())); | |
| std::string postdata2("data"); | |
| request.AppendDataToBody(postdata2.data(), | |
| static_cast<uint32_t>(postdata2.length())); | |
| return LoadAndCompareBody(request, postdata1 + postdata2); | |
| } | |
| std::string TestURLLoader::TestEmptyDataPOST() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod("POST"); | |
| request.AppendDataToBody("", 0); | |
| return LoadAndCompareBody(request, std::string()); | |
| } | |
| std::string TestURLLoader::TestBinaryDataPOST() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod("POST"); | |
| const char postdata_chars[] = | |
| "\x00\x01\x02\x03\x04\x05postdata\xfa\xfb\xfc\xfd\xfe\xff"; | |
| std::string postdata(postdata_chars, | |
| sizeof(postdata_chars) / sizeof(postdata_chars[0])); | |
| request.AppendDataToBody(postdata.data(), | |
| static_cast<uint32_t>(postdata.length())); | |
| return LoadAndCompareBody(request, postdata); | |
| } | |
| std::string TestURLLoader::TestCustomRequestHeader() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echoheader?Foo"); | |
| request.SetHeaders("Foo: 1"); | |
| return LoadAndCompareBody(request, "1"); | |
| } | |
| std::string TestURLLoader::TestFailsBogusContentLength() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echo"); | |
| request.SetMethod("POST"); | |
| request.SetHeaders("Content-Length: 400"); | |
| std::string postdata("postdata"); | |
| request.AppendDataToBody(postdata.data(), | |
| static_cast<uint32_t>(postdata.length())); | |
| int32_t rv; | |
| rv = OpenUntrusted(request, NULL); | |
| if (rv != PP_ERROR_NOACCESS) | |
| return ReportError( | |
| "Untrusted request with bogus Content-Length restriction", rv); | |
| PASS(); | |
| } | |
| std::string TestURLLoader::TestStreamToFile() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("test_url_loader_data/hello.txt"); | |
| ASSERT_FALSE(request.SetStreamToFile(true)); | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| pp::URLLoader loader(instance_); | |
| callback.WaitForResult(loader.Open(request, callback.GetCallback())); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| ASSERT_EQ(PP_OK, callback.result()); | |
| pp::URLResponseInfo response_info(loader.GetResponseInfo()); | |
| if (response_info.is_null()) | |
| return "URLLoader::GetResponseInfo returned null"; | |
| int32_t status_code = response_info.GetStatusCode(); | |
| if (status_code != 200) | |
| return "Unexpected HTTP status code"; | |
| pp::FileRef body(response_info.GetBodyAsFileRef()); | |
| ASSERT_TRUE(body.is_null()); | |
| callback.WaitForResult(loader.FinishStreamingToFile(callback.GetCallback())); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| ASSERT_EQ(PP_ERROR_NOTSUPPORTED, callback.result()); | |
| PASS(); | |
| } | |
| // Untrusted, unintended cross-origin requests should fail. | |
| std::string TestURLLoader::TestUntrustedSameOriginRestriction() { | |
| pp::URLRequestInfo request(instance_); | |
| std::string cross_origin_url = GetReachableCrossOriginURL("test_case.html"); | |
| request.SetURL(cross_origin_url); | |
| int32_t rv = OpenUntrusted(request, NULL); | |
| if (rv != PP_ERROR_NOACCESS) | |
| return ReportError( | |
| "Untrusted, unintended cross-origin request restriction", rv); | |
| PASS(); | |
| } | |
| // Trusted, unintended cross-origin requests should succeed. | |
| std::string TestURLLoader::TestTrustedSameOriginRestriction() { | |
| pp::URLRequestInfo request(instance_); | |
| std::string cross_origin_url = GetReachableCrossOriginURL("test_case.html"); | |
| request.SetURL(cross_origin_url); | |
| int32_t rv = OpenTrusted(request, NULL); | |
| if (rv != PP_OK) | |
| return ReportError("Trusted cross-origin request failed", rv); | |
| PASS(); | |
| } | |
| // Untrusted, intended cross-origin requests should use CORS and succeed. | |
| std::string TestURLLoader::TestUntrustedCrossOriginRequest() { | |
| pp::URLRequestInfo request(instance_); | |
| std::string cross_origin_url = GetReachableCrossOriginURL("test_case.html"); | |
| request.SetURL(cross_origin_url); | |
| request.SetAllowCrossOriginRequests(true); | |
| int32_t rv = OpenUntrusted(request, NULL); | |
| if (rv != PP_OK) | |
| return ReportError( | |
| "Untrusted, intended cross-origin request failed", rv); | |
| PASS(); | |
| } | |
| // Trusted, intended cross-origin requests should use CORS and succeed. | |
| std::string TestURLLoader::TestTrustedCrossOriginRequest() { | |
| pp::URLRequestInfo request(instance_); | |
| std::string cross_origin_url = GetReachableCrossOriginURL("test_case.html"); | |
| request.SetURL(cross_origin_url); | |
| request.SetAllowCrossOriginRequests(true); | |
| int32_t rv = OpenTrusted(request, NULL); | |
| if (rv != PP_OK) | |
| return ReportError("Trusted cross-origin request failed", rv); | |
| PASS(); | |
| } | |
| // CORB (Cross-Origin Read Blocking) should apply to plugins without universal | |
| // access. This test is very similar to TestUntrustedSameOriginRestriction, but | |
| // explicitly uses a CORB-eligible response (test/json + nosniff). | |
| std::string TestURLLoader::TestUntrustedCorbEligibleRequest() { | |
| // It is important to use a CORB-eligible response here: text/json + nosniff. | |
| std::string cross_origin_url = | |
| GetReachableCrossOriginURL("corb_eligible_resource.json"); | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL(cross_origin_url); | |
| request.SetAllowCrossOriginRequests(true); | |
| std::string response_body; | |
| int32_t rv = OpenUntrusted(request, &response_body); | |
| // Main verification - the response should be blocked. Ideally the blocking | |
| // should be done before the data leaves the browser and/or network-service | |
| // process (the test doesn't verify this though). | |
| if (rv != PP_ERROR_NOACCESS) { | |
| return ReportError("Untrusted Javascript URL request restriction failed", | |
| rv); | |
| } | |
| ASSERT_EQ("", response_body); | |
| PASS(); | |
| } | |
| // CORB (Cross-Origin Read Blocking) shouldn't apply to plugins with universal | |
| // access (see PepperURLLoaderHost::has_universal_access_) - such plugins may | |
| // have their own CORS-like mechanisms - e.g. crossdomain.xml in Flash). | |
| // This test is quite similar to TestTrustedSameOriginRestriction, but it | |
| // explicitly uses a CORB-eligible response (test/json + nosniff) and also | |
| // explicitly verifies that the response body was not blocked. | |
| std::string TestURLLoader::TestTrustedCorbEligibleRequest() { | |
| // It is important to use a CORB-eligible response here: text/json + nosniff. | |
| std::string cross_origin_url = | |
| GetReachableCrossOriginURL("corb_eligible_resource.json"); | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL(cross_origin_url); | |
| request.SetAllowCrossOriginRequests(true); | |
| std::string response_body; | |
| int32_t rv = OpenTrusted(request, &response_body); | |
| if (rv != PP_OK) | |
| return ReportError("Trusted CORB-eligible request failed", rv); | |
| // Main verification - if CORB blocked the response, then |response_body| | |
| // would be empty. | |
| ASSERT_EQ("{ \"foo\": \"bar\" }\n", response_body); | |
| PASS(); | |
| } | |
| // Untrusted Javascript URLs requests should fail. | |
| std::string TestURLLoader::TestUntrustedJavascriptURLRestriction() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("javascript:foo = bar"); | |
| int32_t rv = OpenUntrusted(request, NULL); | |
| if (rv != PP_ERROR_NOACCESS) | |
| return ReportError( | |
| "Untrusted Javascript URL request restriction failed", rv); | |
| PASS(); | |
| } | |
| // Trusted Javascript URLs requests should succeed. | |
| std::string TestURLLoader::TestTrustedJavascriptURLRestriction() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("javascript:foo = bar"); | |
| int32_t rv = OpenTrusted(request, NULL); | |
| if (rv == PP_ERROR_NOACCESS) | |
| return ReportError( | |
| "Trusted Javascript URL request", rv); | |
| PASS(); | |
| } | |
| std::string TestURLLoader::TestUntrustedHttpRequests() { | |
| // HTTP methods are restricted only for untrusted loaders. Forbidden | |
| // methods are CONNECT, TRACE, and TRACK, and any string that is not a | |
| // valid token (containing special characters like CR, LF). | |
| // http://www.w3.org/TR/XMLHttpRequest/ | |
| { | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("cOnNeCt", std::string())); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("tRaCk", std::string())); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("tRaCe", std::string())); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, | |
| OpenUntrusted("POST\x0d\x0ax-csrf-token:\x20test1234", std::string())); | |
| } | |
| // HTTP methods are restricted only for untrusted loaders. Try all headers | |
| // that are forbidden by http://www.w3.org/TR/XMLHttpRequest/. | |
| { | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Accept-Charset:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Accept-Encoding:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Connection:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Content-Length:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Cookie:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Cookie2:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Date:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Dnt:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Expect:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Host:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Keep-Alive:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Referer:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "TE:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Trailer:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, | |
| OpenUntrusted("GET", "Transfer-Encoding:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Upgrade:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "User-Agent:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Via:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted( | |
| "GET", "Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==:\n")); | |
| ASSERT_EQ(PP_ERROR_NOACCESS, OpenUntrusted("GET", "Sec-foo:\n")); | |
| } | |
| // Untrusted requests with custom referrer should fail. | |
| { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetCustomReferrerURL("http://www.google.com/"); | |
| int32_t rv = OpenUntrusted(request, NULL); | |
| if (rv != PP_ERROR_NOACCESS) | |
| return ReportError( | |
| "Untrusted request with custom referrer restriction", rv); | |
| } | |
| // Untrusted requests with custom transfer encodings should fail. | |
| { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetCustomContentTransferEncoding("foo"); | |
| int32_t rv = OpenUntrusted(request, NULL); | |
| if (rv != PP_ERROR_NOACCESS) | |
| return ReportError( | |
| "Untrusted request with content-transfer-encoding restriction", rv); | |
| } | |
| PASS(); | |
| } | |
| std::string TestURLLoader::TestTrustedHttpRequests() { | |
| // Trusted requests can use restricted methods. | |
| { | |
| ASSERT_EQ(PP_OK, OpenTrusted("cOnNeCt", std::string())); | |
| ASSERT_EQ(PP_OK, OpenTrusted("tRaCk", std::string())); | |
| ASSERT_EQ(PP_OK, OpenTrusted("tRaCe", std::string())); | |
| } | |
| // Trusted requests can use restricted headers. | |
| { | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Accept-Charset:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Accept-Encoding:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Connection:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Content-Length:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Cookie:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Cookie2:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Content-Transfer-Encoding:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Date:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Expect:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Host:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Keep-Alive:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Referer:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "TE:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Trailer:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Transfer-Encoding:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Upgrade:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "User-Agent:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Via:\n")); | |
| ASSERT_EQ(PP_OK, | |
| OpenTrusted("GET", | |
| "Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==:\n")); | |
| ASSERT_EQ(PP_OK, OpenTrusted("GET", "Sec-foo:\n")); | |
| } | |
| // Trusted requests with custom referrer should succeed. | |
| { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetCustomReferrerURL("http://www.google.com/"); | |
| int32_t rv = OpenTrusted(request, NULL); | |
| if (rv != PP_OK) | |
| return ReportError("Trusted request with custom referrer", rv); | |
| } | |
| // Trusted requests with custom transfer encodings should succeed. | |
| { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetCustomContentTransferEncoding("foo"); | |
| int32_t rv = OpenTrusted(request, NULL); | |
| if (rv != PP_OK) | |
| return ReportError( | |
| "Trusted request with content-transfer-encoding failed", rv); | |
| } | |
| PASS(); | |
| } | |
| // This test should cause a redirect and ensure that the loader follows it. | |
| std::string TestURLLoader::TestFollowURLRedirect() { | |
| pp::URLRequestInfo request(instance_); | |
| // This prefix causes the test server to return a 301 redirect. | |
| std::string redirect_prefix("/server-redirect?"); | |
| // We need an absolute path for the redirect to actually work. | |
| std::string redirect_url = | |
| GetReachableAbsoluteURL("test_url_loader_data/hello.txt"); | |
| request.SetURL(redirect_prefix.append(redirect_url)); | |
| return LoadAndCompareBody(request, "hello\n"); | |
| } | |
| // This test should cause a redirect and ensure that the loader runs | |
| // the callback, rather than following the redirect. | |
| std::string TestURLLoader::TestAuditURLRedirect() { | |
| pp::URLRequestInfo request(instance_); | |
| // This path will cause the server to return a 301 redirect. | |
| // This prefix causes the test server to return a 301 redirect. | |
| std::string redirect_prefix("/server-redirect?"); | |
| // We need an absolute path for the redirect to actually work. | |
| std::string redirect_url = | |
| GetReachableAbsoluteURL("test_url_loader_data/hello.txt"); | |
| request.SetURL(redirect_prefix.append(redirect_url)); | |
| request.SetFollowRedirects(false); | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| pp::URLLoader loader(instance_); | |
| callback.WaitForResult(loader.Open(request, callback.GetCallback())); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| ASSERT_EQ(PP_OK, callback.result()); | |
| // Checks that the response indicates a redirect, and that the URL | |
| // is correct. | |
| pp::URLResponseInfo response_info(loader.GetResponseInfo()); | |
| if (response_info.is_null()) | |
| return "URLLoader::GetResponseInfo returned null"; | |
| int32_t status_code = response_info.GetStatusCode(); | |
| if (status_code != 301) | |
| return "Response status should be 301"; | |
| // Test that the paused loader can be resumed. | |
| callback.WaitForResult(loader.FollowRedirect(callback.GetCallback())); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| ASSERT_EQ(PP_OK, callback.result()); | |
| std::string body; | |
| std::string error = ReadEntireResponseBody(&loader, &body); | |
| if (!error.empty()) | |
| return error; | |
| if (body != "hello\n") | |
| return "URLLoader::FollowRedirect failed"; | |
| PASS(); | |
| } | |
| std::string TestURLLoader::TestAbortCalls() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("test_url_loader_data/hello.txt"); | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| int32_t rv; | |
| // Abort |Open()|. | |
| { | |
| rv = pp::URLLoader(instance_).Open(request, callback.GetCallback()); | |
| } | |
| callback.WaitForAbortResult(rv); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| // Abort |ReadResponseBody()|. | |
| { | |
| char buf[2] = { 0 }; | |
| { | |
| pp::URLLoader loader(instance_); | |
| callback.WaitForResult(loader.Open(request, callback.GetCallback())); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| ASSERT_EQ(PP_OK, callback.result()); | |
| rv = loader.ReadResponseBody(buf, sizeof(buf), callback.GetCallback()); | |
| } // Destroy |loader|. | |
| callback.WaitForAbortResult(rv); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| if (rv == PP_OK_COMPLETIONPENDING) { | |
| if (buf[0] || buf[1]) { | |
| return "URLLoader::ReadResponseBody wrote data after resource " | |
| "destruction."; | |
| } | |
| } | |
| } | |
| // TODO(viettrungluu): More abort tests (but add basic tests first). | |
| // Also test that Close() aborts properly. crbug.com/69457 | |
| PASS(); | |
| } | |
| std::string TestURLLoader::TestUntendedLoad() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("test_url_loader_data/hello.txt"); | |
| request.SetRecordDownloadProgress(true); | |
| TestCompletionCallback callback(instance_->pp_instance(), callback_type()); | |
| pp::URLLoader loader(instance_); | |
| callback.WaitForResult(loader.Open(request, callback.GetCallback())); | |
| CHECK_CALLBACK_BEHAVIOR(callback); | |
| ASSERT_EQ(PP_OK, callback.result()); | |
| // We received the response callback. Yield until the network code has called | |
| // the loader's didReceiveData and didFinishLoading methods before we give it | |
| // another callback function, to make sure the loader works with no callback. | |
| int64_t bytes_received = 0; | |
| int64_t total_bytes_to_be_received = 0; | |
| while (true) { | |
| loader.GetDownloadProgress(&bytes_received, &total_bytes_to_be_received); | |
| if (total_bytes_to_be_received <= 0) | |
| return ReportError("URLLoader::GetDownloadProgress total size", | |
| static_cast<int32_t>(total_bytes_to_be_received)); | |
| if (bytes_received == total_bytes_to_be_received) | |
| break; | |
| // Yield if we're on the main thread, so that URLLoader can receive more | |
| // data. | |
| if (pp::Module::Get()->core()->IsMainThread()) { | |
| NestedEvent event(instance_->pp_instance()); | |
| event.PostSignal(10); | |
| event.Wait(); | |
| } | |
| } | |
| // The loader should now have the data and have finished successfully. | |
| std::string body; | |
| std::string error = ReadEntireResponseBody(&loader, &body); | |
| if (!error.empty()) | |
| return error; | |
| if (body != "hello\n") | |
| return ReportError("Couldn't read data", callback.result()); | |
| PASS(); | |
| } | |
| int32_t TestURLLoader::OpenWithPrefetchBufferThreshold(int32_t lower, | |
| int32_t upper) { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("test_url_loader_data/hello.txt"); | |
| request.SetPrefetchBufferLowerThreshold(lower); | |
| request.SetPrefetchBufferUpperThreshold(upper); | |
| return OpenUntrusted(request, NULL); | |
| } | |
| std::string TestURLLoader::TestPrefetchBufferThreshold() { | |
| int32_t rv = OpenWithPrefetchBufferThreshold(-1, 1); | |
| if (rv != PP_ERROR_FAILED) { | |
| return ReportError("The prefetch limits contained a negative value but " | |
| "the URLLoader did not fail.", rv); | |
| } | |
| rv = OpenWithPrefetchBufferThreshold(0, 1); | |
| if (rv != PP_OK) { | |
| return ReportError("The prefetch buffer limits were legal values but " | |
| "the URLLoader failed.", rv); | |
| } | |
| rv = OpenWithPrefetchBufferThreshold(1000, 1); | |
| if (rv != PP_ERROR_FAILED) { | |
| return ReportError("The lower buffer value was higher than the upper but " | |
| "the URLLoader did not fail.", rv); | |
| } | |
| PASS(); | |
| } | |
| // TODO(viettrungluu): This test properly belongs elsewhere. It tests that | |
| // Chrome properly tags URL requests made on behalf of Pepper plugins (with an | |
| // X-Requested-With header), but this isn't, strictly speaking, a PPAPI | |
| // behavior. | |
| std::string TestURLLoader::TestXRequestedWithHeader() { | |
| pp::URLRequestInfo request(instance_); | |
| request.SetURL("/echoheader?X-Requested-With"); | |
| // The name and version of the plugin is set from the command-line (see | |
| // chrome/test/ppapi/ppapi_test.cc. | |
| return LoadAndCompareBody(request, "PPAPITests/1.2.3"); | |
| } | |
| // TODO(viettrungluu): Add tests for Get{Upload,Download}Progress, Close | |
| // (including abort tests if applicable). |