OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 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 "websocket_blob_receiver.h" |
| 6 |
| 7 #include <stddef.h> |
| 8 #include <string.h> |
| 9 #include <ostream> |
| 10 #include <utility> |
| 11 |
| 12 #include "base/bind.h" |
| 13 #include "base/bind_helpers.h" |
| 14 #include "base/files/file_util.h" |
| 15 #include "base/guid.h" |
| 16 #include "base/location.h" |
| 17 #include "base/logging.h" |
| 18 #include "base/message_loop/message_loop.h" |
| 19 #include "base/numerics/safe_conversions.h" |
| 20 #include "base/single_thread_task_runner.h" |
| 21 #include "content/browser/loader/temporary_file_stream.h" |
| 22 #include "content/public/browser/browser_thread.h" |
| 23 #include "net/base/file_stream.h" |
| 24 #include "net/base/io_buffer.h" |
| 25 #include "net/base/net_errors.h" |
| 26 #include "storage/browser/blob/blob_data_builder.h" |
| 27 #include "storage/browser/blob/blob_storage_context.h" |
| 28 #include "storage/browser/blob/shareable_file_reference.h" |
| 29 |
| 30 namespace content { |
| 31 |
| 32 namespace { |
| 33 // This should be the same as the maximum receive quota provided by the |
| 34 // renderer, otherwise WebSocketHost will have to do unnecessary buffering. |
| 35 const size_t kBufferSize = 64 * 1024; |
| 36 } |
| 37 |
| 38 // Needed to make CHECK_EQ(), etc. work |
| 39 std::ostream& operator<<(std::ostream& os, WebSocketBlobReceiver::State state) { |
| 40 static const char* const kStateStrings[] = { |
| 41 "NONE", |
| 42 "CREATE_FILE", |
| 43 "CREATE_FILE_COMPLETE", |
| 44 "SEND_QUOTA", |
| 45 "WRITE", |
| 46 "WRITE_COMPLETE", |
| 47 "GET_INFO", |
| 48 "GET_INFO_COMPLETE", |
| 49 }; |
| 50 |
| 51 if (state < WebSocketBlobReceiver::State::NONE || |
| 52 state > WebSocketBlobReceiver::State::GET_INFO_COMPLETE) { |
| 53 return os << "Bad state (" << static_cast<int>(state) << ")"; |
| 54 } |
| 55 return os << kStateStrings[static_cast<int>(state)]; |
| 56 } |
| 57 |
| 58 // Helper object to call GetFileInfo() on the FILE thread |
| 59 class WebSocketBlobReceiver::FileInfoHelper { |
| 60 public: |
| 61 FileInfoHelper(const base::WeakPtr<WebSocketBlobReceiver>& owner) |
| 62 : owner_(owner) {} |
| 63 |
| 64 void GetFileInfo(const base::FilePath& path) { |
| 65 result_ = base::GetFileInfo(path, &info_); |
| 66 } |
| 67 |
| 68 void DidGetInfo() { |
| 69 if (owner_) |
| 70 owner_->DidGetInfo(result_, info_); |
| 71 } |
| 72 |
| 73 private: |
| 74 const base::WeakPtr<WebSocketBlobReceiver> owner_; |
| 75 bool result_ = false; |
| 76 base::File::Info info_; |
| 77 |
| 78 DISALLOW_COPY_AND_ASSIGN(FileInfoHelper); |
| 79 }; |
| 80 |
| 81 WebSocketBlobReceiver::WebSocketBlobReceiver( |
| 82 scoped_ptr<Client> client, |
| 83 storage::BlobStorageContext* blob_storage_context) |
| 84 : client_(std::move(client)), |
| 85 blob_storage_context_(blob_storage_context), |
| 86 io_buffer_(new net::DrainableIOBuffer(new net::IOBuffer(kBufferSize), |
| 87 kBufferSize)), |
| 88 pending_quota_(kBufferSize), |
| 89 weak_factory_(this) { |
| 90 DCHECK(blob_storage_context_); |
| 91 io_buffer_->SetOffset(kBufferSize); |
| 92 } |
| 93 |
| 94 WebSocketBlobReceiver::~WebSocketBlobReceiver() {} |
| 95 |
| 96 void WebSocketBlobReceiver::Start() { |
| 97 // This doesn't limit the space used by a single origin or take into account |
| 98 // incognito mode. TODO(ricea): Apply quota and take into account incognito |
| 99 // mode. |
| 100 next_state_ = State::CREATE_FILE; |
| 101 int rv = DoLoop(net::OK); |
| 102 DCHECK_EQ(net::ERR_IO_PENDING, rv); |
| 103 } |
| 104 |
| 105 int WebSocketBlobReceiver::AppendData(const std::vector<char>& data) { |
| 106 CHECK_LE(data.size(), kBufferSize); |
| 107 DCHECK_NE(next_state_, State::NONE); |
| 108 if (data.size() == 0) |
| 109 return net::OK; |
| 110 if (io_in_progress_) { |
| 111 pending_data_.insert(pending_data_.end(), data.begin(), data.end()); |
| 112 return net::ERR_IO_PENDING; |
| 113 } |
| 114 PrepareWrite(data); |
| 115 return DoLoop(net::OK); |
| 116 } |
| 117 |
| 118 int WebSocketBlobReceiver::Finish() { |
| 119 DCHECK(!finish_called_); |
| 120 finish_called_ = true; |
| 121 DCHECK_NE(next_state_, State::NONE); |
| 122 DCHECK_NE(next_state_, State::GET_INFO); |
| 123 DCHECK_NE(next_state_, State::GET_INFO_COMPLETE); |
| 124 if (io_in_progress_) |
| 125 return net::ERR_IO_PENDING; |
| 126 return DoLoop(net::OK); |
| 127 } |
| 128 |
| 129 int WebSocketBlobReceiver::DoLoop(int result) { |
| 130 int rv = result; |
| 131 do { |
| 132 State state = next_state_; |
| 133 next_state_ = State::NONE; |
| 134 switch (state) { |
| 135 case State::CREATE_FILE: |
| 136 DCHECK_EQ(net::OK, rv); |
| 137 rv = DoCreateFile(); |
| 138 break; |
| 139 |
| 140 case State::CREATE_FILE_COMPLETE: |
| 141 rv = DoCreateFileComplete(rv); |
| 142 break; |
| 143 |
| 144 case State::SEND_QUOTA: |
| 145 DCHECK_EQ(net::OK, rv); |
| 146 rv = DoSendQuota(); |
| 147 break; |
| 148 |
| 149 case State::WRITE: |
| 150 DCHECK_EQ(net::OK, rv); |
| 151 rv = DoWrite(); |
| 152 break; |
| 153 |
| 154 case State::WRITE_COMPLETE: |
| 155 rv = DoWriteComplete(rv); |
| 156 break; |
| 157 |
| 158 case State::GET_INFO: |
| 159 DCHECK_EQ(net::OK, rv); |
| 160 rv = DoGetInfo(); |
| 161 break; |
| 162 |
| 163 case State::GET_INFO_COMPLETE: |
| 164 rv = DoGetInfoComplete(rv); |
| 165 break; |
| 166 |
| 167 default: |
| 168 NOTREACHED(); |
| 169 } |
| 170 } while (rv != net::ERR_IO_PENDING && next_state_ != State::NONE); |
| 171 return rv; |
| 172 } |
| 173 |
| 174 void WebSocketBlobReceiver::DoLoopAsync(int result) { |
| 175 int rv = DoLoop(result); |
| 176 if (rv == net::ERR_IO_PENDING) |
| 177 return; |
| 178 if (rv < 0) |
| 179 client_->BlobFailed(rv); |
| 180 } |
| 181 |
| 182 int WebSocketBlobReceiver::DoCreateFile() { |
| 183 next_state_ = State::CREATE_FILE_COMPLETE; |
| 184 io_in_progress_ = true; |
| 185 CreateTemporaryFileStream( |
| 186 base::Bind(&WebSocketBlobReceiver::DidCreateTemporaryFileStream, |
| 187 weak_factory_.GetWeakPtr())); |
| 188 return net::ERR_IO_PENDING; |
| 189 } |
| 190 |
| 191 int WebSocketBlobReceiver::DoCreateFileComplete(int result) { |
| 192 io_in_progress_ = false; |
| 193 if (result < 0) |
| 194 return result; |
| 195 next_state_ = State::SEND_QUOTA; |
| 196 return result; |
| 197 } |
| 198 |
| 199 int WebSocketBlobReceiver::DoSendQuota() { |
| 200 next_state_ = State::WRITE; |
| 201 if (finish_called_) |
| 202 return net::OK; |
| 203 DCHECK_GT(pending_quota_, 0u); |
| 204 base::MessageLoop::current()->PostTask( |
| 205 FROM_HERE, base::Bind(&WebSocketBlobReceiver::DoSendQuotaAsync, |
| 206 weak_factory_.GetWeakPtr(), pending_quota_)); |
| 207 pending_quota_ = 0u; |
| 208 return net::ERR_IO_PENDING; |
| 209 } |
| 210 |
| 211 int WebSocketBlobReceiver::DoWrite() { |
| 212 next_state_ = State::WRITE_COMPLETE; |
| 213 io_in_progress_ = true; |
| 214 if (io_buffer_->BytesRemaining() == 0) |
| 215 return net::OK; |
| 216 // This use of base::Unretained() is safe because |file_stream_| will cancel |
| 217 // writing on destruction, and its lifetime is tied to |
| 218 // |deletable_file_|. |deletable_file_| was created by this object, and |
| 219 // remains solely owned by this object until BlobCreated() is called. |
| 220 // BlobCreated() will not be called while |io_in_progress_| is true. |
| 221 return file_stream_->Write(io_buffer_.get(), io_buffer_->BytesRemaining(), |
| 222 base::Bind(&WebSocketBlobReceiver::OnWriteComplete, |
| 223 base::Unretained(this))); |
| 224 } |
| 225 |
| 226 int WebSocketBlobReceiver::DoWriteComplete(int result) { |
| 227 io_in_progress_ = false; |
| 228 if (result < 0) |
| 229 return result; |
| 230 if (result > 0) |
| 231 io_buffer_->DidConsume(result); |
| 232 if (io_buffer_->BytesRemaining() > 0) { |
| 233 next_state_ = State::WRITE; |
| 234 return net::OK; |
| 235 } |
| 236 if (!pending_data_.empty()) { |
| 237 next_state_ = State::WRITE; |
| 238 std::vector<char> pending_data; |
| 239 pending_data.swap(pending_data_); |
| 240 PrepareWrite(pending_data); |
| 241 return net::OK; |
| 242 } |
| 243 if (finish_called_) { |
| 244 next_state_ = State::GET_INFO; |
| 245 return net::OK; |
| 246 } |
| 247 DCHECK_GT(pending_quota_, 0u); |
| 248 next_state_ = State::SEND_QUOTA; |
| 249 return net::OK; |
| 250 } |
| 251 |
| 252 int WebSocketBlobReceiver::DoGetInfo() { |
| 253 next_state_ = State::GET_INFO_COMPLETE; |
| 254 scoped_refptr<base::SingleThreadTaskRunner> file_thread = |
| 255 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::FILE); |
| 256 FileInfoHelper* file_info_helper = |
| 257 new FileInfoHelper(weak_factory_.GetWeakPtr()); |
| 258 file_thread->PostTaskAndReply( |
| 259 FROM_HERE, |
| 260 base::Bind(&FileInfoHelper::GetFileInfo, |
| 261 base::Unretained(file_info_helper), deletable_file_->path()), |
| 262 base::Bind(&FileInfoHelper::DidGetInfo, base::Owned(file_info_helper))); |
| 263 return net::ERR_IO_PENDING; |
| 264 } |
| 265 |
| 266 int WebSocketBlobReceiver::DoGetInfoComplete(int result) { |
| 267 return result; |
| 268 } |
| 269 |
| 270 void WebSocketBlobReceiver::OnWriteComplete(int result) { |
| 271 DoLoopAsync(result); |
| 272 // |this| may be destroyed here. |
| 273 } |
| 274 |
| 275 void WebSocketBlobReceiver::DidCreateTemporaryFileStream( |
| 276 base::File::Error error_code, |
| 277 scoped_ptr<net::FileStream> file_stream, |
| 278 storage::ShareableFileReference* deletable_file) { |
| 279 if (error_code == base::File::FILE_OK) { |
| 280 file_stream_.swap(file_stream); |
| 281 deletable_file_ = deletable_file; |
| 282 } |
| 283 DoLoopAsync(net::FileErrorToNetError(error_code)); |
| 284 // |this| may be destroyed here. |
| 285 } |
| 286 |
| 287 void WebSocketBlobReceiver::PrepareWrite(const std::vector<char>& data) { |
| 288 CHECK_LE(data.size(), kBufferSize); |
| 289 pending_quota_ += data.size(); |
| 290 io_buffer_->SetOffset(kBufferSize - data.size()); |
| 291 memcpy(io_buffer_->data(), data.data(), data.size()); |
| 292 } |
| 293 |
| 294 void WebSocketBlobReceiver::DidGetInfo(bool result, |
| 295 const base::File::Info& info) { |
| 296 if (result) { |
| 297 storage::BlobDataBuilder builder(base::GenerateGUID()); |
| 298 builder.AppendFile(deletable_file_->path(), UINT64_C(0), |
| 299 base::checked_cast<uint64_t>(info.size), |
| 300 info.last_modified); |
| 301 scoped_ptr<storage::BlobDataHandle> blob_data_handle( |
| 302 blob_storage_context_->AddFinishedBlob(builder)); |
| 303 if (!blob_data_handle) { |
| 304 DoLoopAsync(net::ERR_OUT_OF_MEMORY); |
| 305 // |this| may be destroyed here. |
| 306 return; |
| 307 } |
| 308 client_->BlobCreated(std::move(blob_data_handle), info.size); |
| 309 // |this| may be destroyed here. |
| 310 return; |
| 311 } |
| 312 DoLoopAsync(net::ERR_FILE_NOT_FOUND); |
| 313 // |this| may be destroyed here. |
| 314 } |
| 315 |
| 316 // This is called asynchronously rather than from the DoLoop() because it may |
| 317 // destroy |this|. |
| 318 void WebSocketBlobReceiver::DoSendQuotaAsync(size_t quota) { |
| 319 client_->AddFlowControlQuota(quota); |
| 320 } |
| 321 |
| 322 } // namespace content |
OLD | NEW |