1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "net/url_request/url_request_file_job.h" 6 7 #include "base/files/file_util.h" 8 #include "base/files/scoped_temp_dir.h" 9 #include "base/memory/scoped_ptr.h" 10 #include "base/run_loop.h" 11 #include "base/strings/stringprintf.h" 12 #include "base/threading/sequenced_worker_pool.h" 13 #include "net/base/filename_util.h" 14 #include "net/base/net_util.h" 15 #include "net/url_request/url_request.h" 16 #include "net/url_request/url_request_test_util.h" 17 #include "testing/gtest/include/gtest/gtest.h" 18 19 namespace net { 20 21 namespace { 22 23 // A URLRequestFileJob for testing OnSeekComplete / OnReadComplete callbacks. 24 class URLRequestFileJobWithCallbacks : public URLRequestFileJob { 25 public: 26 URLRequestFileJobWithCallbacks( 27 URLRequest* request, 28 NetworkDelegate* network_delegate, 29 const base::FilePath& file_path, 30 const scoped_refptr<base::TaskRunner>& file_task_runner) 31 : URLRequestFileJob(request, 32 network_delegate, 33 file_path, 34 file_task_runner), 35 seek_position_(0) { 36 } 37 38 int64 seek_position() { return seek_position_; } 39 const std::vector<std::string>& data_chunks() { return data_chunks_; } 40 41 protected: 42 virtual ~URLRequestFileJobWithCallbacks() {} 43 44 virtual void OnSeekComplete(int64 result) OVERRIDE { 45 ASSERT_EQ(seek_position_, 0); 46 seek_position_ = result; 47 } 48 49 virtual void OnReadComplete(IOBuffer* buf, int result) OVERRIDE { 50 data_chunks_.push_back(std::string(buf->data(), result)); 51 } 52 53 int64 seek_position_; 54 std::vector<std::string> data_chunks_; 55 }; 56 57 // A URLRequestJobFactory that will return URLRequestFileJobWithCallbacks 58 // instances for file:// scheme URLs. 59 class CallbacksJobFactory : public URLRequestJobFactory { 60 public: 61 class JobObserver { 62 public: 63 virtual void OnJobCreated(URLRequestFileJobWithCallbacks* job) = 0; 64 }; 65 66 CallbacksJobFactory(const base::FilePath& path, JobObserver* observer) 67 : path_(path), observer_(observer) { 68 } 69 70 virtual ~CallbacksJobFactory() {} 71 72 virtual URLRequestJob* MaybeCreateJobWithProtocolHandler( 73 const std::string& scheme, 74 URLRequest* request, 75 NetworkDelegate* network_delegate) const OVERRIDE { 76 URLRequestFileJobWithCallbacks* job = new URLRequestFileJobWithCallbacks( 77 request, 78 network_delegate, 79 path_, 80 base::MessageLoop::current()->message_loop_proxy()); 81 observer_->OnJobCreated(job); 82 return job; 83 } 84 85 virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE { 86 return scheme == "file"; 87 } 88 89 virtual bool IsHandledURL(const GURL& url) const OVERRIDE { 90 return IsHandledProtocol(url.scheme()); 91 } 92 93 virtual bool IsSafeRedirectTarget(const GURL& location) const OVERRIDE { 94 return false; 95 } 96 97 private: 98 base::FilePath path_; 99 JobObserver* observer_; 100 }; 101 102 // Helper function to create a file in |directory| filled with 103 // |content|. Returns true on succes and fills in |path| with the full path to 104 // the file. 105 bool CreateTempFileWithContent(const std::string& content, 106 const base::ScopedTempDir& directory, 107 base::FilePath* path) { 108 if (!directory.IsValid()) 109 return false; 110 111 if (!base::CreateTemporaryFileInDir(directory.path(), path)) 112 return false; 113 114 return base::WriteFile(*path, content.c_str(), content.length()); 115 } 116 117 class JobObserverImpl : public CallbacksJobFactory::JobObserver { 118 public: 119 virtual void OnJobCreated(URLRequestFileJobWithCallbacks* job) OVERRIDE { 120 jobs_.push_back(job); 121 } 122 123 typedef std::vector<scoped_refptr<URLRequestFileJobWithCallbacks> > JobList; 124 125 const JobList& jobs() { return jobs_; } 126 127 protected: 128 JobList jobs_; 129 }; 130 131 // A simple holder for start/end used in http range requests. 132 struct Range { 133 int start; 134 int end; 135 136 Range() { 137 start = 0; 138 end = 0; 139 } 140 141 Range(int start, int end) { 142 this->start = start; 143 this->end = end; 144 } 145 }; 146 147 // A superclass for tests of the OnSeekComplete / OnReadComplete functions of 148 // URLRequestFileJob. 149 class URLRequestFileJobEventsTest : public testing::Test { 150 public: 151 URLRequestFileJobEventsTest(); 152 153 protected: 154 // This creates a file with |content| as the contents, and then creates and 155 // runs a URLRequestFileJobWithCallbacks job to get the contents out of it, 156 // and makes sure that the callbacks observed the correct bytes. If a Range 157 // is provided, this function will add the appropriate Range http header to 158 // the request and verify that only the bytes in that range (inclusive) were 159 // observed. 160 void RunRequest(const std::string& content, const Range* range); 161 162 JobObserverImpl observer_; 163 TestURLRequestContext context_; 164 TestDelegate delegate_; 165 }; 166 167 URLRequestFileJobEventsTest::URLRequestFileJobEventsTest() {} 168 169 void URLRequestFileJobEventsTest::RunRequest(const std::string& content, 170 const Range* range) { 171 base::ScopedTempDir directory; 172 ASSERT_TRUE(directory.CreateUniqueTempDir()); 173 base::FilePath path; 174 ASSERT_TRUE(CreateTempFileWithContent(content, directory, &path)); 175 CallbacksJobFactory factory(path, &observer_); 176 context_.set_job_factory(&factory); 177 178 scoped_ptr<URLRequest> request(context_.CreateRequest( 179 FilePathToFileURL(path), DEFAULT_PRIORITY, &delegate_, NULL)); 180 if (range) { 181 ASSERT_GE(range->start, 0); 182 ASSERT_GE(range->end, 0); 183 ASSERT_LE(range->start, range->end); 184 ASSERT_LT(static_cast<unsigned int>(range->end), content.length()); 185 std::string range_value = 186 base::StringPrintf("bytes=%d-%d", range->start, range->end); 187 request->SetExtraRequestHeaderByName( 188 HttpRequestHeaders::kRange, range_value, true /*overwrite*/); 189 } 190 request->Start(); 191 192 base::RunLoop loop; 193 loop.Run(); 194 195 EXPECT_FALSE(delegate_.request_failed()); 196 int expected_length = 197 range ? (range->end - range->start + 1) : content.length(); 198 EXPECT_EQ(delegate_.bytes_received(), expected_length); 199 200 std::string expected_content; 201 if (range) { 202 expected_content.insert(0, content, range->start, expected_length); 203 } else { 204 expected_content = content; 205 } 206 EXPECT_TRUE(delegate_.data_received() == expected_content); 207 208 ASSERT_EQ(observer_.jobs().size(), 1u); 209 ASSERT_EQ(observer_.jobs().at(0)->seek_position(), range ? range->start : 0); 210 211 std::string observed_content; 212 const std::vector<std::string>& chunks = 213 observer_.jobs().at(0)->data_chunks(); 214 for (std::vector<std::string>::const_iterator i = chunks.begin(); 215 i != chunks.end(); 216 ++i) { 217 observed_content.append(*i); 218 } 219 EXPECT_EQ(expected_content, observed_content); 220 } 221 222 // Helper function to make a character array filled with |size| bytes of 223 // test content. 224 std::string MakeContentOfSize(int size) { 225 EXPECT_GE(size, 0); 226 std::string result; 227 result.reserve(size); 228 for (int i = 0; i < size; i++) { 229 result.append(1, static_cast<char>(i % 256)); 230 } 231 return result; 232 } 233 234 TEST_F(URLRequestFileJobEventsTest, TinyFile) { 235 RunRequest(std::string("hello world"), NULL); 236 } 237 238 TEST_F(URLRequestFileJobEventsTest, SmallFile) { 239 RunRequest(MakeContentOfSize(17 * 1024), NULL); 240 } 241 242 TEST_F(URLRequestFileJobEventsTest, BigFile) { 243 RunRequest(MakeContentOfSize(3 * 1024 * 1024), NULL); 244 } 245 246 TEST_F(URLRequestFileJobEventsTest, Range) { 247 // Use a 15KB content file and read a range chosen somewhat arbitrarily but 248 // not aligned on any likely page boundaries. 249 int size = 15 * 1024; 250 Range range(1701, (6 * 1024) + 3); 251 RunRequest(MakeContentOfSize(size), &range); 252 } 253 254 } // namespace 255 256 } // namespace net 257