| // 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 "chrome/browser/chromeos/gdata/drive_protocol_handler.h" |
| |
| #include <algorithm> |
| #include <string> |
| #include <vector> |
| |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/file_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/string_number_conversions.h" |
| #include "base/string_util.h" |
| #include "base/threading/sequenced_worker_pool.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/chromeos/gdata/drive.pb.h" |
| #include "chrome/browser/chromeos/gdata/drive_file_system_interface.h" |
| #include "chrome/browser/chromeos/gdata/drive_service_interface.h" |
| #include "chrome/browser/chromeos/gdata/drive_system_service.h" |
| #include "chrome/browser/google_apis/gdata_errorcode.h" |
| #include "chrome/browser/google_apis/gdata_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/common/url_constants.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/escape.h" |
| #include "net/base/file_stream.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_response_info.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_job.h" |
| |
| using content::BrowserThread; |
| |
| namespace gdata { |
| |
| namespace { |
| |
| const net::UnescapeRule::Type kUrlPathUnescapeMask = |
| net::UnescapeRule::SPACES | |
| net::UnescapeRule::URL_SPECIAL_CHARS | |
| net::UnescapeRule::CONTROL_CHARS; |
| |
| const int kHTTPOk = 200; |
| const int kHTTPNotAllowed = 403; |
| const int kHTTPNotFound = 404; |
| const int kHTTPInternalError = 500; |
| |
| const char kHTTPOkText[] = "OK"; |
| const char kHTTPNotAllowedText[] = "Not Allowed"; |
| const char kHTTPNotFoundText[] = "Not Found"; |
| const char kHTTPInternalErrorText[] = "Internal Error"; |
| |
| const int64 kInvalidFileSize = -1; |
| |
| // Initial size of download buffer, same as kBufferSize used for URLFetcherCore. |
| const int kInitialDownloadBufferSizeInBytes = 4096; |
| |
| struct MimeTypeReplacement { |
| const char* original_type; |
| const char* new_type; |
| }; |
| |
| const MimeTypeReplacement kMimeTypeReplacements[] = { |
| {"message/rfc822", "multipart/related"} // Fixes MHTML |
| }; |
| |
| std::string FixupMimeType(const std::string& type) { |
| for (size_t i = 0; i < arraysize(kMimeTypeReplacements); i++) { |
| if (type == kMimeTypeReplacements[i].original_type) |
| return kMimeTypeReplacements[i].new_type; |
| } |
| return type; |
| } |
| |
| // Empty callback for net::FileStream::Close(). |
| void EmptyCompletionCallback(int) { |
| } |
| |
| // Helper function that reads file size. |
| void GetFileSizeOnBlockingPool(const FilePath& file_path, |
| int64* file_size) { |
| if (!file_util::GetFileSize(file_path, file_size)) |
| *file_size = kInvalidFileSize; |
| } |
| |
| // Helper function that extracts and unescapes resource_id from drive urls |
| // (drive:<resource_id>). |
| bool ParseDriveUrl(const std::string& path, std::string* resource_id) { |
| const std::string drive_schema(chrome::kDriveScheme + std::string(":")); |
| if (!StartsWithASCII(path, drive_schema, false) || |
| path.size() <= drive_schema.size()) { |
| return false; |
| } |
| |
| std::string id = path.substr(drive_schema.size()); |
| *resource_id = net::UnescapeURLComponent(id, kUrlPathUnescapeMask); |
| return resource_id->size(); |
| } |
| |
| // Helper function to get DriveSystemService from Profile. |
| DriveSystemService* GetSystemService() { |
| return DriveSystemServiceFactory::GetForProfile( |
| ProfileManager::GetDefaultProfile()); |
| } |
| |
| // Helper function to get DriveFileSystem from Profile on UI thread. |
| void GetFileSystemOnUIThread(DriveFileSystemInterface** file_system) { |
| DriveSystemService* system_service = GetSystemService(); |
| *file_system = system_service ? system_service->file_system() : NULL; |
| } |
| |
| // Helper function to cancel Drive download operation on UI thread. |
| void CancelDriveDownloadOnUIThread(const FilePath& drive_file_path) { |
| DriveSystemService* system_service = GetSystemService(); |
| if (system_service) |
| system_service->drive_service()->CancelForFilePath(drive_file_path); |
| } |
| |
| // DriveURLRequesetJob is the gateway between network-level drive://... |
| // requests for drive resources and DriveFileSytem. It exposes content URLs |
| // formatted as drive://<resource-id>. |
| class DriveURLRequestJob : public net::URLRequestJob { |
| public: |
| DriveURLRequestJob(net::URLRequest* request, |
| net::NetworkDelegate* network_delegate); |
| |
| // net::URLRequestJob overrides: |
| virtual void Start() OVERRIDE; |
| virtual void Kill() OVERRIDE; |
| virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; |
| virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; |
| virtual int GetResponseCode() const OVERRIDE; |
| virtual bool ReadRawData(net::IOBuffer* buf, |
| int buf_size, |
| int* bytes_read) OVERRIDE; |
| |
| protected: |
| virtual ~DriveURLRequestJob(); |
| |
| private: |
| // Helper for Start() to let us start asynchronously. |
| void StartAsync(DriveFileSystemInterface** file_system); |
| |
| // Helper methods for Delegate::OnUrlFetchDownloadData and ReadRawData to |
| // receive download data and copy to response buffer. |
| // For detailed description of logic, refer to comments in definitions of |
| // Start() and ReadRawData(). |
| |
| void OnUrlFetchDownloadData(GDataErrorCode error, |
| scoped_ptr<std::string> download_data); |
| // Called from ReadRawData, returns true if data is ready, false otherwise. |
| bool ContinueReadFromDownloadData(int* bytes_read); |
| // Copies from download buffer into response buffer. |
| bool ReadFromDownloadData(); |
| |
| // Helper callback for handling async responses from |
| // DriveFileSystem::GetFileByResourceId(). |
| void OnGetFileByResourceId(DriveFileError error, |
| const FilePath& local_file_path, |
| const std::string& mime_type, |
| DriveFileType file_type); |
| |
| // Helper callback for GetFileSizeOnBlockingPool that sets |remaining_bytes_| |
| // to |file_size|, and notifies result for Start(). |
| void OnGetFileSize(int64 *file_size); |
| |
| // Helper callback for GetEntryInfoByResourceId invoked by StartAsync. |
| void OnGetEntryInfoByResourceId(const std::string& resource_id, |
| DriveFileError error, |
| const FilePath& drive_file_path, |
| scoped_ptr<DriveEntryProto> entry_proto); |
| |
| // Helper methods for ReadRawData to open file and read from its corresponding |
| // stream in a streaming fashion. |
| bool ContinueReadFromFile(int* bytes_read); |
| void ReadFromFile(); |
| void ReadFileStream(int bytes_to_read); |
| |
| // Helper callback for handling async responses from FileStream::Open(). |
| void OnFileOpen(int bytes_to_read, int open_result); |
| |
| // Helper callback for handling async responses from FileStream::Read(). |
| void OnReadFileStream(int bytes_read); |
| |
| // Helper methods to handle |reamining_bytes_| and |read_buf_|. |
| int BytesReadCompleted(); |
| void RecordBytesRead(int bytes_read); |
| |
| // Helper methods to formulate and notify about response status, info and |
| // headers. |
| void NotifySuccess(); |
| void NotifyFailure(int); |
| void HeadersCompleted(int status_code, const std::string& status_txt); |
| |
| // Helper method to close |stream_|. |
| void CloseFileStream(); |
| |
| DriveFileSystemInterface* file_system_; |
| |
| bool error_; // True if we've encountered an error. |
| bool headers_set_; // True if headers have been set. |
| |
| FilePath local_file_path_; |
| FilePath drive_file_path_; |
| std::string mime_type_; |
| int64 initial_file_size_; |
| int64 remaining_bytes_; |
| scoped_ptr<net::FileStream> stream_; |
| scoped_refptr<net::DrainableIOBuffer> read_buf_; |
| scoped_ptr<net::HttpResponseInfo> response_info_; |
| bool streaming_download_; |
| scoped_refptr<net::GrowableIOBuffer> download_growable_buf_; |
| scoped_refptr<net::DrainableIOBuffer> download_drainable_buf_; |
| |
| // This should remain the last member so it'll be destroyed first and |
| // invalidate its weak pointers before other members are destroyed. |
| base::WeakPtrFactory<DriveURLRequestJob> weak_ptr_factory_; |
| DISALLOW_COPY_AND_ASSIGN(DriveURLRequestJob); |
| }; |
| |
| DriveURLRequestJob::DriveURLRequestJob(net::URLRequest* request, |
| net::NetworkDelegate* network_delegate) |
| : net::URLRequestJob(request, network_delegate), |
| file_system_(NULL), |
| error_(false), |
| headers_set_(false), |
| initial_file_size_(0), |
| remaining_bytes_(0), |
| streaming_download_(false), |
| download_growable_buf_(new net::GrowableIOBuffer), |
| ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { |
| download_growable_buf_->SetCapacity(kInitialDownloadBufferSizeInBytes); |
| download_drainable_buf_ = new net::DrainableIOBuffer( |
| download_growable_buf_, download_growable_buf_->capacity()); |
| } |
| |
| void DriveURLRequestJob::Start() { |
| DVLOG(1) << "Starting request"; |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| // As per pattern shared by most net::URLRequestJob implementations, we start |
| // asynchronously. |
| |
| // Here is how Start and its helper methods work: |
| // 1) Post task to UI thread to get file system. |
| // Note: should we not post task to get file system later, start request |
| // should still be asynchronous, so that all error reporting and data |
| // callbacks happen as they would for network requests, so we should still |
| // post task to same thread to actually start request. |
| // 2) Reply task StartAsync is invoked. |
| // 3) If unable to get file system or request method is not GET, report start |
| // error and bail out. |
| // 4) Otherwise, parse request url to get resource id and file name. |
| // 5) Find file from file system to get its mime type, drive file path and |
| // size of physical file. |
| // 6) Get file from file system asynchronously with both GetFileCallback and |
| // GetContentCallback - this would either get it from cache or |
| // download it from Drive. |
| // 7) If file is downloaded from Drive: |
| // 7.1) Whenever net::URLFetcherCore::OnReadCompleted() receives a part |
| // of the response, it invokes |
| // net::URLFetcherDelegate::OnURLFetchDownloadData() if |
| // net::URLFetcherDelegate::ShouldSendDownloadData() is true. |
| // 7.2) gdata::DownloadFileOperation overrides the default implementations |
| // of the following methods of net::URLFetcherDelegate: |
| // - ShouldSendDownloadData(): returns true for non-null |
| // GetContentCallback. |
| // - OnURLFetchDownloadData(): invokes non-null |
| // GetContentCallback |
| // 7.3) DriveProtolHandler::OnURLFetchDownloadData (i.e. this class) |
| // is at the end of the invocation chain and actually implements the |
| // method. |
| // 7.4) Copies the formal download data into a growable-drainable download |
| // IOBuffer |
| // - IOBuffer has initial size 4096, same as buffer used in |
| // net::URLFetcherCore::OnReadCompleted. |
| // - We may end up with multiple chunks, so we use GrowableIOBuffer. |
| // - We then wrap the growable buffer within a DrainableIOBuffer for |
| // ease of progressively writing into the buffer. |
| // 7.5) When we receive the very first chunk of data, notify start success. |
| // 7.6) Proceed to streaming of download data in ReadRawData. |
| // 8) If file exits in cache: |
| // 8.1) in get-file callback, post task to get file size of local file. |
| // 8.2) In get-file-size callback, record file size and notify success. |
| // 8.3) Proceed to reading from file in ReadRawData. |
| // Any error arising from steps 4-8, immediately report start error and bail |
| // out. |
| // NotifySuccess internally calls ReadRawData, hence we only notify success |
| // after we have: |
| // - received the first chunk of download data if file is downloaded |
| // - gotten size of physical file if file exists in cache. |
| |
| // Request job is created and runs on IO thread but getting file system via |
| // profile needs to happen on UI thread, so post GetFileSystemOnUIThread to |
| // UI thread; StartAsync reply task will proceed with actually starting the |
| // request. |
| |
| DriveFileSystemInterface** file_system = new DriveFileSystemInterface*(NULL); |
| BrowserThread::PostTaskAndReply( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&GetFileSystemOnUIThread, file_system), |
| base::Bind(&DriveURLRequestJob::StartAsync, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Owned(file_system))); |
| } |
| |
| void DriveURLRequestJob::Kill() { |
| DVLOG(1) << "Killing request"; |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| CloseFileStream(); |
| |
| // If download operation for drive file (via |
| // DriveFileSystem::GetFileByResourceId) is still in progress, cancel it by |
| // posting a task on the UI thread. |
| // Download operation is still in progress if: |
| // 1) |local_file_path_| is still empty; it gets filled when callback for |
| // GetFileByResourceId is called, AND |
| // 2) we're still streaming download data i.e. |remaining_bytes_| > 0; if |
| // we've finished streaming data, we want to avoid possibly killing last |
| // part of the download process where the last chunk is written to file; |
| // if we're reading directly from cache file, |remaining_bytes_| doesn't |
| // matter 'cos |local_file_path_| will not be empty. |
| if (file_system_ && !drive_file_path_.empty() && local_file_path_.empty() && |
| remaining_bytes_ > 0) { |
| DVLOG(1) << "Canceling download operation for " << drive_file_path_.value(); |
| BrowserThread::PostTask( |
| BrowserThread::UI, |
| FROM_HERE, |
| base::Bind(&CancelDriveDownloadOnUIThread, |
| drive_file_path_)); |
| } |
| |
| net::URLRequestJob::Kill(); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| } |
| |
| bool DriveURLRequestJob::GetMimeType(std::string* mime_type) const { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| mime_type->assign(FixupMimeType(mime_type_)); |
| return !mime_type->empty(); |
| } |
| |
| void DriveURLRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| if (response_info_.get()) |
| *info = *response_info_; |
| } |
| |
| int DriveURLRequestJob::GetResponseCode() const { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| if (!response_info_.get()) |
| return -1; |
| |
| return response_info_->headers->response_code(); |
| } |
| |
| bool DriveURLRequestJob::ReadRawData(net::IOBuffer* dest, |
| int dest_size, |
| int* bytes_read) { |
| // ReadRawData splits into 2 logic paths: streaming downloaded file or reading |
| // from physical file, the latter is similar to the reading from file in |
| // webkit_blob::BlobURLRequsetJob. |
| // For reading from existing file, here is how it and its helper methods work: |
| // First time ReadRawData is called: |
| // 1) Open file stream asynchronously. |
| // 2) If there's an immediate error, report failure. |
| // 3) Otherwise, set request status to pending. |
| // 4) Return false for ReadRawData to indicate no data and either error or |
| // open pending |
| // 5) In file-open callback, continue with step 6. |
| // Subsequent times and reading part of first time: |
| // 6) Determine number of bytes to read, which is the lesser of remaining |
| // bytes in read buffer or file. |
| // 7) Read file stream asynchronously. |
| // 8) If there's an immediate error, report failure. |
| // 9) Otherwise, set request status to pending. |
| // 10) Return false for ReadRawData to indicate no data and either error or |
| // read pending. |
| // 11) In file-read callback: |
| // 11.1) Clear pending request status. |
| // 11.2) If read buffer is all filled up, notify read complete. |
| // 11.3) Otherwise, repeat from step 6. |
| // After step 11.2, ReadRawData will be called to read the next chunk. |
| // This repeats until we return true and 0 for bytes_read for ReadRawData. |
| // |
| // For streaming from downloaded file, there's no difference in the |
| // implementation of ReadRawData for first or subsequent times, except where |
| // it is called from. The first ReadRawData is called internally from |
| // NotifySuccess whereas subsequent times are called internally from |
| // NotifyReadComplete. Logic flow is as follows: |
| // 1) When a chunk of data is received in OnUrlFetchDownloadData, copy it into |
| // download buffer. If remaining buffer is not enough to hold entire chunk |
| // of downloaded data, grow buffer to size needed, re-wrap it within |
| // drainable buffer, then copy the download chunk. |
| // 2) If a response buffer from ReadRawData exists, copy from download buffer |
| // to response buffer. |
| // 2.1) If response bufer is filled up: |
| // - if we have to return from ReadRawData to caller, return true to |
| // indicate data's ready. |
| // - otherwise, clear io pending status, and notify read complete. |
| // 2.2) Otherwise if response buffer is not filled up: |
| // - if we have to return from ReadRawData to caller, set io pending |
| // status, and return false to indicate data's not ready. |
| // - wait for the chunk of download data and repeat from step 1. |
| // 3) Otherwise, the next ReadRawData() call will provide the response buffer, |
| // when we would repeat from step 2. |
| // Note that we only notify read complete when the response buffer is all |
| // filled up or it's the last chunk of data. During investigation, I |
| // discovered that if I notify read complete without filling up the response |
| // buffer, ReadRawData gets called less and less, resulting in the download |
| // buffer growing bigger and bigger, which is definitely undesirable for us. |
| |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| DCHECK_NE(dest_size, 0); |
| DCHECK(bytes_read); |
| DCHECK_GE(remaining_bytes_, 0); |
| |
| DVLOG(1) << "ReadRawData: bytes_requested=" << dest_size; |
| |
| // Bail out immediately if we encounter an error. |
| if (error_) { |
| DVLOG(1) << "ReadRawData: has previous error"; |
| *bytes_read = 0; |
| return true; |
| } |
| |
| if (remaining_bytes_ < dest_size) |
| dest_size = static_cast<int>(remaining_bytes_); |
| |
| // If we should copy zero bytes because |remaining_bytes_| is zero, short |
| // circuit here. |
| if (!dest_size) { |
| DVLOG(1) << "ReadRawData: done reading " |
| << local_file_path_.BaseName().RemoveExtension().value(); |
| *bytes_read = 0; |
| return true; |
| } |
| |
| // Keep track of the buffer. |
| DCHECK(!read_buf_); |
| read_buf_ = new net::DrainableIOBuffer(dest, dest_size); |
| |
| bool rc = false; |
| if (streaming_download_) |
| rc = ContinueReadFromDownloadData(bytes_read); |
| else |
| rc = ContinueReadFromFile(bytes_read); |
| DVLOG(1) << "ReadRawData: out with " |
| << (rc ? "has" : "no") |
| << "_data, bytes_read=" << *bytes_read |
| << ", buf_remaining=" |
| << (read_buf_ ? read_buf_->BytesRemaining() : 0) |
| << ", " << (streaming_download_ ? "download" : "file") |
| << "_remaining=" << remaining_bytes_; |
| return rc; |
| } |
| |
| //======================= DriveURLRequestJob protected methods ================ |
| |
| DriveURLRequestJob::~DriveURLRequestJob() { |
| CloseFileStream(); |
| } |
| |
| //======================= DriveURLRequestJob private methods =================== |
| |
| void DriveURLRequestJob::StartAsync(DriveFileSystemInterface** file_system) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| file_system_ = *file_system; |
| |
| if (!request_ || !file_system_) { |
| LOG(WARNING) << "Failed to start request: null " |
| << (!request_ ? "request" : "file system"); |
| NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, |
| net::ERR_FAILED)); |
| return; |
| } |
| |
| // We only support GET request. |
| if (request()->method() != "GET") { |
| LOG(WARNING) << "Failed to start request: GET method is not supported"; |
| NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, |
| net::ERR_METHOD_NOT_SUPPORTED)); |
| return; |
| } |
| |
| std::string resource_id; |
| if (!ParseDriveUrl(request_->url().spec(), &resource_id)) { |
| NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, |
| net::ERR_INVALID_URL)); |
| return; |
| } |
| |
| file_system_->GetEntryInfoByResourceId( |
| resource_id, |
| base::Bind(&DriveURLRequestJob::OnGetEntryInfoByResourceId, |
| weak_ptr_factory_.GetWeakPtr(), |
| resource_id)); |
| } |
| |
| void DriveURLRequestJob::OnGetEntryInfoByResourceId( |
| const std::string& resource_id, |
| DriveFileError error, |
| const FilePath& drive_file_path, |
| scoped_ptr<DriveEntryProto> entry_proto) { |
| if (entry_proto.get() && !entry_proto->has_file_specific_info()) |
| error = DRIVE_FILE_ERROR_NOT_FOUND; |
| |
| if (error == DRIVE_FILE_OK) { |
| DCHECK(entry_proto.get()); |
| mime_type_ = entry_proto->file_specific_info().content_mime_type(); |
| drive_file_path_ = drive_file_path; |
| initial_file_size_ = entry_proto->file_info().size(); |
| } else { |
| mime_type_.clear(); |
| drive_file_path_.clear(); |
| initial_file_size_ = 0; |
| } |
| remaining_bytes_ = initial_file_size_; |
| |
| DVLOG(1) << "Getting file for resource id"; |
| file_system_->GetFileByResourceId( |
| resource_id, |
| base::Bind(&DriveURLRequestJob::OnGetFileByResourceId, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Bind(&DriveURLRequestJob::OnUrlFetchDownloadData, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void DriveURLRequestJob::OnUrlFetchDownloadData( |
| GDataErrorCode error, |
| scoped_ptr<std::string> download_data) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| if (download_data->empty()) |
| return; |
| |
| // If there's insufficient space remaining in download buffer to copy download |
| // data, expand download buffer. |
| int bytes_needed = download_data->length() - |
| download_drainable_buf_->BytesRemaining(); |
| if (bytes_needed > 0) { |
| // Grow the download buffer to accommodate entire download data. |
| download_growable_buf_->SetCapacity(download_drainable_buf_->size() + |
| bytes_needed); |
| // Remember current offset of download buffer, i.e. where we last finished |
| // writing to. |
| int offset = download_drainable_buf_->BytesRemaining(); |
| // Reinitialize drainable buffer with the new size. |
| download_drainable_buf_ = new net::DrainableIOBuffer( |
| download_growable_buf_, download_growable_buf_->capacity()); |
| // Set offset in new drainable buffer to what it was before |
| // reinitialization, so that its data() points to where we last finished |
| // writing to, and the next memcpy will continue from there. |
| download_drainable_buf_->SetOffset(offset); |
| } |
| |
| // Copy from download data into download buffer. |
| memcpy(download_drainable_buf_->data(), download_data->data(), |
| download_data->length()); |
| // Advance offset in download buffer. |
| download_drainable_buf_->DidConsume(download_data->length()); |
| DVLOG(1) << "Got OnUrlFetchDownloadData: in_data_size=" |
| << download_data->length() |
| << ", our_data_size=" << download_drainable_buf_->BytesConsumed(); |
| |
| // If this is the first data we have, report request has started successfully. |
| if (!streaming_download_) { |
| streaming_download_ = true; |
| DVLOG(1) << "Request started successfully: expected_download_size=" |
| << remaining_bytes_; |
| NotifySuccess(); |
| return; |
| } |
| |
| // Otherwise, read from download data into read buffer of response. |
| if (ReadFromDownloadData()) { |
| // Read has completed, report it. |
| SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status. |
| int bytes_read = BytesReadCompleted(); |
| DVLOG(1) << "Completed read: bytes_read=" << bytes_read |
| << ", download_remaining=" << remaining_bytes_; |
| NotifyReadComplete(bytes_read); |
| } |
| } |
| |
| bool DriveURLRequestJob::ContinueReadFromDownloadData(int* bytes_read) { |
| // Continue to read if there's more to read from download data or read buffer |
| // is not filled up. |
| if (remaining_bytes_ > 0 && read_buf_->BytesRemaining() > 0) { |
| if (!ReadFromDownloadData()) { |
| DVLOG(1) << "IO is pending for reading from download data"; |
| SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); |
| // Either async IO is pending or there's an error, return false. |
| return false; |
| } |
| } |
| |
| // Otherwise, we have data in read buffer, return true with number of bytes |
| // read. |
| *bytes_read = BytesReadCompleted(); |
| DVLOG(1) << "Has data: bytes_read=" << *bytes_read |
| << ", buf_remaining=0, download_remaining=" << remaining_bytes_; |
| return true; |
| } |
| |
| bool DriveURLRequestJob::ReadFromDownloadData() { |
| DCHECK(streaming_download_); |
| |
| // If download buffer is empty or there's no read buffer, return false. |
| if (download_drainable_buf_->BytesConsumed() <= 0 || |
| !read_buf_ || read_buf_->BytesRemaining() <= 0) { |
| return false; |
| } |
| |
| // Number of bytes to read is the lesser of remaining bytes in read buffer or |
| // written bytes in download buffer. |
| int bytes_to_read = std::min(read_buf_->BytesRemaining(), |
| download_drainable_buf_->BytesConsumed()); |
| // If read buffer doesn't have enough space, there will be bytes in download |
| // buffer that will not be copied to read buffer. |
| int bytes_not_copied = download_drainable_buf_->BytesConsumed() - |
| bytes_to_read; |
| // Set offset in download buffer to 0, so that its data() points to the |
| // beginning of the buffer where we'll copy bytes from. |
| download_drainable_buf_->SetOffset(0); |
| // Copy from download buffer to read buffer. |
| memcpy(read_buf_->data(), download_drainable_buf_->data(), bytes_to_read); |
| // Advance read buffer. |
| RecordBytesRead(bytes_to_read); |
| // If download buffer has bytes that are not copied over, move them to |
| // beginning of download buffer. |
| if (bytes_not_copied > 0) { |
| // data() is pointing to the beginning, so the bytes to move start from |
| // where we finished copying to read buffer. |
| memmove(download_drainable_buf_->data(), |
| download_drainable_buf_->data() + bytes_to_read, |
| bytes_not_copied); |
| // Set offset in download buffer to where we finished moving. |
| download_drainable_buf_->SetOffset(bytes_not_copied); |
| } |
| DVLOG(1) << "Copied from download data: bytes_read=" << bytes_to_read; |
| |
| // Return true if read buffer is filled up or there's no more bytes to read. |
| return read_buf_->BytesRemaining() == 0 || remaining_bytes_ == 0; |
| } |
| |
| void DriveURLRequestJob::OnGetFileByResourceId( |
| DriveFileError error, |
| const FilePath& local_file_path, |
| const std::string& mime_type, |
| DriveFileType file_type) { |
| DVLOG(1) << "Got OnGetFileByResourceId"; |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| if (error != DRIVE_FILE_OK || file_type != REGULAR_FILE) { |
| LOG(WARNING) << "Failed to start request: can't get file for resource id"; |
| NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, |
| net::ERR_FILE_NOT_FOUND)); |
| return; |
| } |
| |
| local_file_path_ = local_file_path; |
| |
| // If we've already streamed download data to response, we're done. |
| if (streaming_download_) |
| return; |
| |
| // Even though we're already on BrowserThread::IO thread, |
| // file_util::GetFileSize can only be called on a thread with file |
| // operations allowed, so post a task to blocking pool instead. |
| int64* file_size = new int64(kInvalidFileSize); |
| BrowserThread::GetBlockingPool()->PostTaskAndReply( |
| FROM_HERE, |
| base::Bind(&GetFileSizeOnBlockingPool, |
| local_file_path_, |
| base::Unretained(file_size)), |
| base::Bind(&DriveURLRequestJob::OnGetFileSize, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Owned(file_size))); |
| } |
| |
| void DriveURLRequestJob::OnGetFileSize(int64 *file_size) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| |
| if (*file_size == kInvalidFileSize) { |
| LOG(WARNING) << "Failed to open " << local_file_path_.value(); |
| NotifyStartError(net::URLRequestStatus(net::URLRequestStatus::FAILED, |
| net::ERR_FILE_NOT_FOUND)); |
| return; |
| } |
| |
| remaining_bytes_ = *file_size; |
| DVLOG(1) << "Request started successfully: file_size=" << remaining_bytes_; |
| |
| NotifySuccess(); |
| } |
| |
| bool DriveURLRequestJob::ContinueReadFromFile(int* bytes_read) { |
| // Continue to read if there's more to read from file or read buffer is not |
| // filled up. |
| if (remaining_bytes_ > 0 && read_buf_->BytesRemaining() > 0) { |
| ReadFromFile(); |
| // Either async IO is pending or there's an error, return false. |
| return false; |
| } |
| |
| // Otherwise, we have data in read buffer, return true with number of bytes |
| // read. |
| *bytes_read = BytesReadCompleted(); |
| DVLOG(1) << "Has data: bytes_read=" << *bytes_read |
| << ", buf_remaining=0, file_remaining=" << remaining_bytes_; |
| return true; |
| } |
| |
| void DriveURLRequestJob::ReadFromFile() { |
| int bytes_to_read = std::min(read_buf_->BytesRemaining(), |
| static_cast<int>(remaining_bytes_)); |
| |
| // If the stream already exists, keep reading from it. |
| if (stream_.get()) { |
| ReadFileStream(bytes_to_read); |
| return; |
| } |
| |
| // Otherwise, open the stream for file. |
| stream_.reset(new net::FileStream(NULL)); |
| int result = stream_->Open( |
| local_file_path_, |
| base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ | |
| base::PLATFORM_FILE_ASYNC, |
| base::Bind(&DriveURLRequestJob::OnFileOpen, |
| weak_ptr_factory_.GetWeakPtr(), |
| bytes_to_read)); |
| |
| if (result == net::ERR_IO_PENDING) { |
| DVLOG(1) << "IO is pending for opening " |
| << local_file_path_.BaseName().RemoveExtension().value(); |
| SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); |
| } else { |
| DCHECK_NE(result, net::OK); |
| LOG(WARNING) << "Failed to open " << local_file_path_.value(); |
| NotifyFailure(net::ERR_FILE_NOT_FOUND); |
| } |
| } |
| |
| void DriveURLRequestJob::OnFileOpen(int bytes_to_read, int open_result) { |
| if (open_result != net::OK) { |
| LOG(WARNING) << "Failed to open " << local_file_path_.value(); |
| NotifyFailure(net::ERR_FILE_NOT_FOUND); |
| return; |
| } |
| |
| DVLOG(1) << "Successfully opened " << local_file_path_.value(); |
| |
| // Read from opened file stream. |
| DCHECK(stream_.get()); |
| ReadFileStream(bytes_to_read); |
| } |
| |
| void DriveURLRequestJob::ReadFileStream(int bytes_to_read) { |
| DCHECK(stream_.get()); |
| DCHECK(stream_->IsOpen()); |
| DCHECK_GE(read_buf_->BytesRemaining(), bytes_to_read); |
| |
| int result = stream_->Read(read_buf_, bytes_to_read, |
| base::Bind(&DriveURLRequestJob::OnReadFileStream, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| // If IO is pending, we just need to wait. |
| if (result == net::ERR_IO_PENDING) { |
| DVLOG(1) << "IO pending: bytes_to_read=" << bytes_to_read |
| << ", buf_remaining=" << read_buf_->BytesRemaining() |
| << ", file_remaining=" << remaining_bytes_; |
| SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); |
| } else { // For all other errors, bail out. |
| // Asynchronous read should not return result >= 0; |
| // refer to net/base/file_stream_posix.cc. |
| DCHECK(result < 0); |
| LOG(WARNING) << "Failed to read " << local_file_path_.value(); |
| NotifyFailure(net::ERR_FAILED); |
| } |
| } |
| |
| void DriveURLRequestJob::OnReadFileStream(int bytes_read) { |
| if (bytes_read <= 0) { |
| LOG(WARNING) << "Failed to read " << local_file_path_.value(); |
| NotifyFailure(net::ERR_FAILED); |
| return; |
| } |
| |
| SetStatus(net::URLRequestStatus()); // Clear the IO_PENDING status. |
| |
| RecordBytesRead(bytes_read); |
| |
| DVLOG(1) << "Cleared IO pending: bytes_read=" << bytes_read |
| << ", buf_remaining=" << read_buf_->BytesRemaining() |
| << ", file_remaining=" << remaining_bytes_; |
| |
| // If the read buffer is completely filled, we're done. |
| if (!read_buf_->BytesRemaining()) { |
| int bytes_read = BytesReadCompleted(); |
| DVLOG(1) << "Completed read: bytes_read=" << bytes_read |
| << ", file_remaining=" << remaining_bytes_; |
| NotifyReadComplete(bytes_read); |
| return; |
| } |
| |
| // Otherwise, continue the reading. |
| int new_bytes_read = 0; |
| if (ContinueReadFromFile(&new_bytes_read)) { |
| DVLOG(1) << "Completed read: bytes_read=" << bytes_read |
| << ", file_remaining=" << remaining_bytes_; |
| NotifyReadComplete(new_bytes_read); |
| } |
| } |
| |
| int DriveURLRequestJob::BytesReadCompleted() { |
| int bytes_read = read_buf_->BytesConsumed(); |
| read_buf_ = NULL; |
| return bytes_read; |
| } |
| |
| void DriveURLRequestJob::RecordBytesRead(int bytes_read) { |
| DCHECK_GT(bytes_read, 0); |
| |
| // Subtract the remaining bytes. |
| remaining_bytes_ -= bytes_read; |
| DCHECK_GE(remaining_bytes_, 0); |
| |
| // Adjust the read buffer. |
| read_buf_->DidConsume(bytes_read); |
| DCHECK_GE(read_buf_->BytesRemaining(), 0); |
| } |
| |
| void DriveURLRequestJob::CloseFileStream() { |
| if (!stream_.get()) |
| return; |
| stream_->Close(base::Bind(&EmptyCompletionCallback)); |
| // net::FileStream::Close blocks until stream is closed, so it's safe to |
| // release the pointer here. |
| stream_.reset(NULL); |
| } |
| |
| void DriveURLRequestJob::NotifySuccess() { |
| HeadersCompleted(kHTTPOk, kHTTPOkText); |
| } |
| |
| void DriveURLRequestJob::NotifyFailure(int error_code) { |
| error_ = true; |
| |
| // If we already return the headers on success, we can't change the headers |
| // now. Instead, we just error out. |
| if (headers_set_) { |
| NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, |
| error_code)); |
| return; |
| } |
| |
| int status_code = 0; |
| std::string status_txt; |
| switch (error_code) { |
| case net::ERR_ACCESS_DENIED: |
| status_code = kHTTPNotAllowed; |
| status_txt = kHTTPNotAllowedText; |
| break; |
| case net::ERR_FILE_NOT_FOUND: |
| status_code = kHTTPNotFound; |
| status_txt = kHTTPNotFoundText; |
| break; |
| case net::ERR_FAILED: |
| status_code = kHTTPInternalError; |
| status_txt = kHTTPInternalErrorText; |
| break; |
| default: |
| DCHECK(false); |
| status_code = kHTTPInternalError; |
| status_txt = kHTTPInternalErrorText; |
| break; |
| } |
| |
| HeadersCompleted(status_code, status_txt); |
| } |
| |
| void DriveURLRequestJob::HeadersCompleted(int status_code, |
| const std::string& status_text) { |
| std::string status("HTTP/1.1 "); |
| status.append(base::IntToString(status_code)); |
| status.append(" "); |
| status.append(status_text); |
| status.append("\0\0", 2); |
| net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); |
| |
| if (status_code == kHTTPOk) { |
| std::string content_length_header(net::HttpRequestHeaders::kContentLength); |
| content_length_header.append(": "); |
| content_length_header.append(base::Int64ToString(remaining_bytes_)); |
| headers->AddHeader(content_length_header); |
| |
| if (!mime_type_.empty()) { |
| std::string content_type_header(net::HttpRequestHeaders::kContentType); |
| content_type_header.append(": "); |
| content_type_header.append(mime_type_); |
| headers->AddHeader(content_type_header); |
| } |
| } |
| |
| response_info_.reset(new net::HttpResponseInfo()); |
| response_info_->headers = headers; |
| |
| set_expected_content_size(remaining_bytes_); |
| headers_set_ = true; |
| |
| NotifyHeadersComplete(); |
| } |
| |
| } // namespace |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // DriveProtocolHandler class |
| |
| DriveProtocolHandler::DriveProtocolHandler() { |
| } |
| |
| DriveProtocolHandler::~DriveProtocolHandler() { |
| } |
| |
| net::URLRequestJob* DriveProtocolHandler::MaybeCreateJob( |
| net::URLRequest* request, net::NetworkDelegate* network_delegate) const { |
| DVLOG(1) << "Handling url: " << request->url().spec(); |
| return new DriveURLRequestJob(request, network_delegate); |
| } |
| |
| } // namespace gdata |