| // 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 "google_apis/drive/base_requests.h" |
| |
| #include "base/json/json_reader.h" |
| #include "base/location.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task_runner_util.h" |
| #include "base/values.h" |
| #include "google_apis/drive/request_sender.h" |
| #include "google_apis/drive/task_util.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_byte_range.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_util.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "net/url_request/url_request_status.h" |
| |
| using net::URLFetcher; |
| |
| namespace { |
| |
| // Template for optional OAuth2 authorization HTTP header. |
| const char kAuthorizationHeaderFormat[] = "Authorization: Bearer %s"; |
| // Template for GData API version HTTP header. |
| const char kGDataVersionHeader[] = "GData-Version: 3.0"; |
| |
| // Maximum number of attempts for re-authentication per request. |
| const int kMaxReAuthenticateAttemptsPerRequest = 1; |
| |
| // Template for initiate upload of both GData WAPI and Drive API v2. |
| const char kUploadContentType[] = "X-Upload-Content-Type: "; |
| const char kUploadContentLength[] = "X-Upload-Content-Length: "; |
| const char kUploadResponseLocation[] = "location"; |
| |
| // Template for upload data range of both GData WAPI and Drive API v2. |
| const char kUploadContentRange[] = "Content-Range: bytes "; |
| const char kUploadResponseRange[] = "range"; |
| |
| // Parse JSON string to base::Value object. |
| scoped_ptr<base::Value> ParseJsonInternal(const std::string& json) { |
| int error_code = -1; |
| std::string error_message; |
| scoped_ptr<base::Value> value(base::JSONReader::ReadAndReturnError( |
| json, base::JSON_PARSE_RFC, &error_code, &error_message)); |
| |
| if (!value.get()) { |
| std::string trimmed_json; |
| if (json.size() < 80) { |
| trimmed_json = json; |
| } else { |
| // Take the first 50 and the last 10 bytes. |
| trimmed_json = base::StringPrintf( |
| "%s [%s bytes] %s", |
| json.substr(0, 50).c_str(), |
| base::Uint64ToString(json.size() - 60).c_str(), |
| json.substr(json.size() - 10).c_str()); |
| } |
| LOG(WARNING) << "Error while parsing entry response: " << error_message |
| << ", code: " << error_code << ", json:\n" << trimmed_json; |
| } |
| return value.Pass(); |
| } |
| |
| // Returns response headers as a string. Returns a warning message if |
| // |url_fetcher| does not contain a valid response. Used only for debugging. |
| std::string GetResponseHeadersAsString( |
| const URLFetcher* url_fetcher) { |
| // net::HttpResponseHeaders::raw_headers(), as the name implies, stores |
| // all headers in their raw format, i.e each header is null-terminated. |
| // So logging raw_headers() only shows the first header, which is probably |
| // the status line. GetNormalizedHeaders, on the other hand, will show all |
| // the headers, one per line, which is probably what we want. |
| std::string headers; |
| // Check that response code indicates response headers are valid (i.e. not |
| // malformed) before we retrieve the headers. |
| if (url_fetcher->GetResponseCode() == URLFetcher::RESPONSE_CODE_INVALID) { |
| headers.assign("Response headers are malformed!!"); |
| } else { |
| url_fetcher->GetResponseHeaders()->GetNormalizedHeaders(&headers); |
| } |
| return headers; |
| } |
| |
| bool IsSuccessfulResponseCode(int response_code) { |
| return 200 <= response_code && response_code <= 299; |
| } |
| |
| } // namespace |
| |
| namespace google_apis { |
| |
| void ParseJson(base::TaskRunner* blocking_task_runner, |
| const std::string& json, |
| const ParseJsonCallback& callback) { |
| base::PostTaskAndReplyWithResult( |
| blocking_task_runner, |
| FROM_HERE, |
| base::Bind(&ParseJsonInternal, json), |
| callback); |
| } |
| |
| //=========================== ResponseWriter ================================== |
| ResponseWriter::ResponseWriter(base::SequencedTaskRunner* file_task_runner, |
| const base::FilePath& file_path, |
| const GetContentCallback& get_content_callback) |
| : get_content_callback_(get_content_callback), |
| weak_ptr_factory_(this) { |
| if (!file_path.empty()) { |
| file_writer_.reset( |
| new net::URLFetcherFileWriter(file_task_runner, file_path)); |
| } |
| } |
| |
| ResponseWriter::~ResponseWriter() { |
| } |
| |
| void ResponseWriter::DisownFile() { |
| DCHECK(file_writer_); |
| file_writer_->DisownFile(); |
| } |
| |
| int ResponseWriter::Initialize(const net::CompletionCallback& callback) { |
| if (file_writer_) |
| return file_writer_->Initialize(callback); |
| |
| data_.clear(); |
| return net::OK; |
| } |
| |
| int ResponseWriter::Write(net::IOBuffer* buffer, |
| int num_bytes, |
| const net::CompletionCallback& callback) { |
| if (!get_content_callback_.is_null()) { |
| get_content_callback_.Run( |
| HTTP_SUCCESS, |
| make_scoped_ptr(new std::string(buffer->data(), num_bytes))); |
| } |
| |
| if (file_writer_) { |
| const int result = file_writer_->Write( |
| buffer, num_bytes, |
| base::Bind(&ResponseWriter::DidWrite, |
| weak_ptr_factory_.GetWeakPtr(), |
| make_scoped_refptr(buffer), callback)); |
| if (result != net::ERR_IO_PENDING) |
| DidWrite(buffer, net::CompletionCallback(), result); |
| return result; |
| } |
| |
| data_.append(buffer->data(), num_bytes); |
| return num_bytes; |
| } |
| |
| int ResponseWriter::Finish(const net::CompletionCallback& callback) { |
| if (file_writer_) |
| return file_writer_->Finish(callback); |
| |
| return net::OK; |
| } |
| |
| void ResponseWriter::DidWrite(scoped_refptr<net::IOBuffer> buffer, |
| const net::CompletionCallback& callback, |
| int result) { |
| if (result > 0) { |
| // Even if file_writer_ is used, append the data to |data_|, so that it can |
| // be used to get error information in case of server side errors. |
| // The size limit is to avoid consuming too much redundant memory. |
| const size_t kMaxStringSize = 1024*1024; |
| if (data_.size() < kMaxStringSize) { |
| data_.append(buffer->data(), std::min(static_cast<size_t>(result), |
| kMaxStringSize - data_.size())); |
| } |
| } |
| |
| if (!callback.is_null()) |
| callback.Run(result); |
| } |
| |
| //============================ UrlFetchRequestBase =========================== |
| |
| UrlFetchRequestBase::UrlFetchRequestBase(RequestSender* sender) |
| : re_authenticate_count_(0), |
| sender_(sender), |
| error_code_(GDATA_OTHER_ERROR), |
| weak_ptr_factory_(this) { |
| } |
| |
| UrlFetchRequestBase::~UrlFetchRequestBase() {} |
| |
| void UrlFetchRequestBase::Start(const std::string& access_token, |
| const std::string& custom_user_agent, |
| const ReAuthenticateCallback& callback) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!access_token.empty()); |
| DCHECK(!callback.is_null()); |
| DCHECK(re_authenticate_callback_.is_null()); |
| |
| re_authenticate_callback_ = callback; |
| |
| GURL url = GetURL(); |
| if (url.is_empty()) { |
| // Error is found on generating the url. Send the error message to the |
| // callback, and then return immediately without trying to connect |
| // to the server. |
| RunCallbackOnPrematureFailure(GDATA_OTHER_ERROR); |
| return; |
| } |
| DVLOG(1) << "URL: " << url.spec(); |
| |
| URLFetcher::RequestType request_type = GetRequestType(); |
| url_fetcher_.reset( |
| URLFetcher::Create(url, request_type, this)); |
| url_fetcher_->SetRequestContext(sender_->url_request_context_getter()); |
| // Always set flags to neither send nor save cookies. |
| url_fetcher_->SetLoadFlags( |
| net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES | |
| net::LOAD_DISABLE_CACHE); |
| |
| base::FilePath output_file_path; |
| GetContentCallback get_content_callback; |
| GetOutputFilePath(&output_file_path, &get_content_callback); |
| if (!get_content_callback.is_null()) |
| get_content_callback = CreateRelayCallback(get_content_callback); |
| response_writer_ = new ResponseWriter(blocking_task_runner(), |
| output_file_path, |
| get_content_callback); |
| url_fetcher_->SaveResponseWithWriter( |
| scoped_ptr<net::URLFetcherResponseWriter>(response_writer_)); |
| |
| // Add request headers. |
| // Note that SetExtraRequestHeaders clears the current headers and sets it |
| // to the passed-in headers, so calling it for each header will result in |
| // only the last header being set in request headers. |
| if (!custom_user_agent.empty()) |
| url_fetcher_->AddExtraRequestHeader("User-Agent: " + custom_user_agent); |
| url_fetcher_->AddExtraRequestHeader(kGDataVersionHeader); |
| url_fetcher_->AddExtraRequestHeader( |
| base::StringPrintf(kAuthorizationHeaderFormat, access_token.data())); |
| std::vector<std::string> headers = GetExtraRequestHeaders(); |
| for (size_t i = 0; i < headers.size(); ++i) { |
| url_fetcher_->AddExtraRequestHeader(headers[i]); |
| DVLOG(1) << "Extra header: " << headers[i]; |
| } |
| |
| // Set upload data if available. |
| std::string upload_content_type; |
| std::string upload_content; |
| if (GetContentData(&upload_content_type, &upload_content)) { |
| url_fetcher_->SetUploadData(upload_content_type, upload_content); |
| } else { |
| base::FilePath local_file_path; |
| int64 range_offset = 0; |
| int64 range_length = 0; |
| if (GetContentFile(&local_file_path, &range_offset, &range_length, |
| &upload_content_type)) { |
| url_fetcher_->SetUploadFilePath( |
| upload_content_type, |
| local_file_path, |
| range_offset, |
| range_length, |
| blocking_task_runner()); |
| } else { |
| // Even if there is no content data, UrlFetcher requires to set empty |
| // upload data string for POST, PUT and PATCH methods, explicitly. |
| // It is because that most requests of those methods have non-empty |
| // body, and UrlFetcher checks whether it is actually not forgotten. |
| if (request_type == URLFetcher::POST || |
| request_type == URLFetcher::PUT || |
| request_type == URLFetcher::PATCH) { |
| // Set empty upload content-type and upload content, so that |
| // the request will have no "Content-type: " header and no content. |
| url_fetcher_->SetUploadData(std::string(), std::string()); |
| } |
| } |
| } |
| |
| url_fetcher_->Start(); |
| } |
| |
| URLFetcher::RequestType UrlFetchRequestBase::GetRequestType() const { |
| return URLFetcher::GET; |
| } |
| |
| std::vector<std::string> UrlFetchRequestBase::GetExtraRequestHeaders() const { |
| return std::vector<std::string>(); |
| } |
| |
| bool UrlFetchRequestBase::GetContentData(std::string* upload_content_type, |
| std::string* upload_content) { |
| return false; |
| } |
| |
| bool UrlFetchRequestBase::GetContentFile(base::FilePath* local_file_path, |
| int64* range_offset, |
| int64* range_length, |
| std::string* upload_content_type) { |
| return false; |
| } |
| |
| void UrlFetchRequestBase::GetOutputFilePath( |
| base::FilePath* local_file_path, |
| GetContentCallback* get_content_callback) { |
| } |
| |
| void UrlFetchRequestBase::Cancel() { |
| response_writer_ = NULL; |
| url_fetcher_.reset(NULL); |
| RunCallbackOnPrematureFailure(GDATA_CANCELLED); |
| sender_->RequestFinished(this); |
| } |
| |
| GDataErrorCode UrlFetchRequestBase::GetErrorCode() { |
| return error_code_; |
| } |
| |
| bool UrlFetchRequestBase::CalledOnValidThread() { |
| return thread_checker_.CalledOnValidThread(); |
| } |
| |
| base::SequencedTaskRunner* UrlFetchRequestBase::blocking_task_runner() const { |
| return sender_->blocking_task_runner(); |
| } |
| |
| void UrlFetchRequestBase::OnProcessURLFetchResultsComplete() { |
| sender_->RequestFinished(this); |
| } |
| |
| void UrlFetchRequestBase::OnURLFetchComplete(const URLFetcher* source) { |
| DVLOG(1) << "Response headers:\n" << GetResponseHeadersAsString(source); |
| |
| // Determine error code. |
| error_code_ = static_cast<GDataErrorCode>(source->GetResponseCode()); |
| if (!source->GetStatus().is_success()) { |
| switch (source->GetStatus().error()) { |
| case net::ERR_NETWORK_CHANGED: |
| error_code_ = GDATA_NO_CONNECTION; |
| break; |
| default: |
| error_code_ = GDATA_OTHER_ERROR; |
| } |
| } |
| |
| // The server may return detailed error status in JSON. |
| // See https://developers.google.com/drive/handle-errors |
| if (!IsSuccessfulResponseCode(error_code_)) { |
| DVLOG(1) << response_writer_->data(); |
| |
| const char kErrorKey[] = "error"; |
| const char kErrorErrorsKey[] = "errors"; |
| const char kErrorReasonKey[] = "reason"; |
| const char kErrorMessageKey[] = "message"; |
| const char kErrorReasonRateLimitExceeded[] = "rateLimitExceeded"; |
| const char kErrorReasonUserRateLimitExceeded[] = "userRateLimitExceeded"; |
| |
| scoped_ptr<base::Value> value(ParseJsonInternal(response_writer_->data())); |
| base::DictionaryValue* dictionary = NULL; |
| base::DictionaryValue* error = NULL; |
| if (value && |
| value->GetAsDictionary(&dictionary) && |
| dictionary->GetDictionaryWithoutPathExpansion(kErrorKey, &error)) { |
| // Get error message. |
| std::string message; |
| error->GetStringWithoutPathExpansion(kErrorMessageKey, &message); |
| DLOG(ERROR) << "code: " << error_code_ << ", message: " << message; |
| |
| // Override the error code based on the reason of the first error. |
| base::ListValue* errors = NULL; |
| base::DictionaryValue* first_error = NULL; |
| if (error->GetListWithoutPathExpansion(kErrorErrorsKey, &errors) && |
| errors->GetDictionary(0, &first_error)) { |
| std::string reason; |
| first_error->GetStringWithoutPathExpansion(kErrorReasonKey, &reason); |
| if (reason == kErrorReasonRateLimitExceeded || |
| reason == kErrorReasonUserRateLimitExceeded) |
| error_code_ = HTTP_SERVICE_UNAVAILABLE; |
| } |
| } |
| } |
| |
| // Handle authentication failure. |
| if (error_code_ == HTTP_UNAUTHORIZED) { |
| if (++re_authenticate_count_ <= kMaxReAuthenticateAttemptsPerRequest) { |
| // Reset re_authenticate_callback_ so Start() can be called again. |
| ReAuthenticateCallback callback = re_authenticate_callback_; |
| re_authenticate_callback_.Reset(); |
| callback.Run(this); |
| return; |
| } |
| |
| OnAuthFailed(error_code_); |
| return; |
| } |
| |
| // Overridden by each specialization |
| ProcessURLFetchResults(source); |
| } |
| |
| void UrlFetchRequestBase::OnAuthFailed(GDataErrorCode code) { |
| RunCallbackOnPrematureFailure(code); |
| sender_->RequestFinished(this); |
| } |
| |
| base::WeakPtr<AuthenticatedRequestInterface> |
| UrlFetchRequestBase::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| //============================ EntryActionRequest ============================ |
| |
| EntryActionRequest::EntryActionRequest(RequestSender* sender, |
| const EntryActionCallback& callback) |
| : UrlFetchRequestBase(sender), |
| callback_(callback) { |
| DCHECK(!callback_.is_null()); |
| } |
| |
| EntryActionRequest::~EntryActionRequest() {} |
| |
| void EntryActionRequest::ProcessURLFetchResults(const URLFetcher* source) { |
| callback_.Run(GetErrorCode()); |
| OnProcessURLFetchResultsComplete(); |
| } |
| |
| void EntryActionRequest::RunCallbackOnPrematureFailure(GDataErrorCode code) { |
| callback_.Run(code); |
| } |
| |
| //============================== GetDataRequest ============================== |
| |
| GetDataRequest::GetDataRequest(RequestSender* sender, |
| const GetDataCallback& callback) |
| : UrlFetchRequestBase(sender), |
| callback_(callback), |
| weak_ptr_factory_(this) { |
| DCHECK(!callback_.is_null()); |
| } |
| |
| GetDataRequest::~GetDataRequest() {} |
| |
| void GetDataRequest::ParseResponse(GDataErrorCode fetch_error_code, |
| const std::string& data) { |
| DCHECK(CalledOnValidThread()); |
| |
| VLOG(1) << "JSON received from " << GetURL().spec() << ": " |
| << data.size() << " bytes"; |
| ParseJson(blocking_task_runner(), |
| data, |
| base::Bind(&GetDataRequest::OnDataParsed, |
| weak_ptr_factory_.GetWeakPtr(), |
| fetch_error_code)); |
| } |
| |
| void GetDataRequest::ProcessURLFetchResults(const URLFetcher* source) { |
| GDataErrorCode fetch_error_code = GetErrorCode(); |
| |
| switch (fetch_error_code) { |
| case HTTP_SUCCESS: |
| case HTTP_CREATED: |
| ParseResponse(fetch_error_code, response_writer()->data()); |
| break; |
| default: |
| RunCallbackOnPrematureFailure(fetch_error_code); |
| OnProcessURLFetchResultsComplete(); |
| break; |
| } |
| } |
| |
| void GetDataRequest::RunCallbackOnPrematureFailure( |
| GDataErrorCode fetch_error_code) { |
| callback_.Run(fetch_error_code, scoped_ptr<base::Value>()); |
| } |
| |
| void GetDataRequest::OnDataParsed(GDataErrorCode fetch_error_code, |
| scoped_ptr<base::Value> value) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (!value.get()) |
| fetch_error_code = GDATA_PARSE_ERROR; |
| |
| callback_.Run(fetch_error_code, value.Pass()); |
| OnProcessURLFetchResultsComplete(); |
| } |
| |
| //========================= InitiateUploadRequestBase ======================== |
| |
| InitiateUploadRequestBase::InitiateUploadRequestBase( |
| RequestSender* sender, |
| const InitiateUploadCallback& callback, |
| const std::string& content_type, |
| int64 content_length) |
| : UrlFetchRequestBase(sender), |
| callback_(callback), |
| content_type_(content_type), |
| content_length_(content_length) { |
| DCHECK(!callback_.is_null()); |
| DCHECK(!content_type_.empty()); |
| DCHECK_GE(content_length_, 0); |
| } |
| |
| InitiateUploadRequestBase::~InitiateUploadRequestBase() {} |
| |
| void InitiateUploadRequestBase::ProcessURLFetchResults( |
| const URLFetcher* source) { |
| GDataErrorCode code = GetErrorCode(); |
| |
| std::string upload_location; |
| if (code == HTTP_SUCCESS) { |
| // Retrieve value of the first "Location" header. |
| source->GetResponseHeaders()->EnumerateHeader(NULL, |
| kUploadResponseLocation, |
| &upload_location); |
| } |
| |
| callback_.Run(code, GURL(upload_location)); |
| OnProcessURLFetchResultsComplete(); |
| } |
| |
| void InitiateUploadRequestBase::RunCallbackOnPrematureFailure( |
| GDataErrorCode code) { |
| callback_.Run(code, GURL()); |
| } |
| |
| std::vector<std::string> |
| InitiateUploadRequestBase::GetExtraRequestHeaders() const { |
| std::vector<std::string> headers; |
| headers.push_back(kUploadContentType + content_type_); |
| headers.push_back( |
| kUploadContentLength + base::Int64ToString(content_length_)); |
| return headers; |
| } |
| |
| //============================ UploadRangeResponse ============================= |
| |
| UploadRangeResponse::UploadRangeResponse() |
| : code(HTTP_SUCCESS), |
| start_position_received(0), |
| end_position_received(0) { |
| } |
| |
| UploadRangeResponse::UploadRangeResponse(GDataErrorCode code, |
| int64 start_position_received, |
| int64 end_position_received) |
| : code(code), |
| start_position_received(start_position_received), |
| end_position_received(end_position_received) { |
| } |
| |
| UploadRangeResponse::~UploadRangeResponse() { |
| } |
| |
| //========================== UploadRangeRequestBase ========================== |
| |
| UploadRangeRequestBase::UploadRangeRequestBase(RequestSender* sender, |
| const GURL& upload_url) |
| : UrlFetchRequestBase(sender), |
| upload_url_(upload_url), |
| weak_ptr_factory_(this) { |
| } |
| |
| UploadRangeRequestBase::~UploadRangeRequestBase() {} |
| |
| GURL UploadRangeRequestBase::GetURL() const { |
| // This is very tricky to get json from this request. To do that, &alt=json |
| // has to be appended not here but in InitiateUploadRequestBase::GetURL(). |
| return upload_url_; |
| } |
| |
| URLFetcher::RequestType UploadRangeRequestBase::GetRequestType() const { |
| return URLFetcher::PUT; |
| } |
| |
| void UploadRangeRequestBase::ProcessURLFetchResults( |
| const URLFetcher* source) { |
| GDataErrorCode code = GetErrorCode(); |
| net::HttpResponseHeaders* hdrs = source->GetResponseHeaders(); |
| |
| if (code == HTTP_RESUME_INCOMPLETE) { |
| // Retrieve value of the first "Range" header. |
| // The Range header is appeared only if there is at least one received |
| // byte. So, initialize the positions by 0 so that the [0,0) will be |
| // returned via the |callback_| for empty data case. |
| int64 start_position_received = 0; |
| int64 end_position_received = 0; |
| std::string range_received; |
| hdrs->EnumerateHeader(NULL, kUploadResponseRange, &range_received); |
| if (!range_received.empty()) { // Parse the range header. |
| std::vector<net::HttpByteRange> ranges; |
| if (net::HttpUtil::ParseRangeHeader(range_received, &ranges) && |
| !ranges.empty() ) { |
| // We only care about the first start-end pair in the range. |
| // |
| // Range header represents the range inclusively, while we are treating |
| // ranges exclusively (i.e., end_position_received should be one passed |
| // the last valid index). So "+ 1" is added. |
| start_position_received = ranges[0].first_byte_position(); |
| end_position_received = ranges[0].last_byte_position() + 1; |
| } |
| } |
| // The Range header has the received data range, so the start position |
| // should be always 0. |
| DCHECK_EQ(start_position_received, 0); |
| |
| OnRangeRequestComplete(UploadRangeResponse(code, |
| start_position_received, |
| end_position_received), |
| scoped_ptr<base::Value>()); |
| |
| OnProcessURLFetchResultsComplete(); |
| } else if (code == HTTP_CREATED || code == HTTP_SUCCESS) { |
| // The upload is successfully done. Parse the response which should be |
| // the entry's metadata. |
| ParseJson(blocking_task_runner(), |
| response_writer()->data(), |
| base::Bind(&UploadRangeRequestBase::OnDataParsed, |
| weak_ptr_factory_.GetWeakPtr(), |
| code)); |
| } else { |
| // Failed to upload. Run callbacks to notify the error. |
| OnRangeRequestComplete( |
| UploadRangeResponse(code, -1, -1), scoped_ptr<base::Value>()); |
| OnProcessURLFetchResultsComplete(); |
| } |
| } |
| |
| void UploadRangeRequestBase::OnDataParsed(GDataErrorCode code, |
| scoped_ptr<base::Value> value) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(code == HTTP_CREATED || code == HTTP_SUCCESS); |
| |
| OnRangeRequestComplete(UploadRangeResponse(code, -1, -1), value.Pass()); |
| OnProcessURLFetchResultsComplete(); |
| } |
| |
| void UploadRangeRequestBase::RunCallbackOnPrematureFailure( |
| GDataErrorCode code) { |
| OnRangeRequestComplete( |
| UploadRangeResponse(code, 0, 0), scoped_ptr<base::Value>()); |
| } |
| |
| //========================== ResumeUploadRequestBase ========================= |
| |
| ResumeUploadRequestBase::ResumeUploadRequestBase( |
| RequestSender* sender, |
| const GURL& upload_location, |
| int64 start_position, |
| int64 end_position, |
| int64 content_length, |
| const std::string& content_type, |
| const base::FilePath& local_file_path) |
| : UploadRangeRequestBase(sender, upload_location), |
| start_position_(start_position), |
| end_position_(end_position), |
| content_length_(content_length), |
| content_type_(content_type), |
| local_file_path_(local_file_path) { |
| DCHECK_LE(start_position_, end_position_); |
| } |
| |
| ResumeUploadRequestBase::~ResumeUploadRequestBase() {} |
| |
| std::vector<std::string> |
| ResumeUploadRequestBase::GetExtraRequestHeaders() const { |
| if (content_length_ == 0) { |
| // For uploading an empty document, just PUT an empty content. |
| DCHECK_EQ(start_position_, 0); |
| DCHECK_EQ(end_position_, 0); |
| return std::vector<std::string>(); |
| } |
| |
| // The header looks like |
| // Content-Range: bytes <start_position>-<end_position>/<content_length> |
| // for example: |
| // Content-Range: bytes 7864320-8388607/13851821 |
| // The header takes inclusive range, so we adjust by "end_position - 1". |
| DCHECK_GE(start_position_, 0); |
| DCHECK_GT(end_position_, 0); |
| DCHECK_GE(content_length_, 0); |
| |
| std::vector<std::string> headers; |
| headers.push_back( |
| std::string(kUploadContentRange) + |
| base::Int64ToString(start_position_) + "-" + |
| base::Int64ToString(end_position_ - 1) + "/" + |
| base::Int64ToString(content_length_)); |
| return headers; |
| } |
| |
| bool ResumeUploadRequestBase::GetContentFile( |
| base::FilePath* local_file_path, |
| int64* range_offset, |
| int64* range_length, |
| std::string* upload_content_type) { |
| if (start_position_ == end_position_) { |
| // No content data. |
| return false; |
| } |
| |
| *local_file_path = local_file_path_; |
| *range_offset = start_position_; |
| *range_length = end_position_ - start_position_; |
| *upload_content_type = content_type_; |
| return true; |
| } |
| |
| //======================== GetUploadStatusRequestBase ======================== |
| |
| GetUploadStatusRequestBase::GetUploadStatusRequestBase(RequestSender* sender, |
| const GURL& upload_url, |
| int64 content_length) |
| : UploadRangeRequestBase(sender, upload_url), |
| content_length_(content_length) {} |
| |
| GetUploadStatusRequestBase::~GetUploadStatusRequestBase() {} |
| |
| std::vector<std::string> |
| GetUploadStatusRequestBase::GetExtraRequestHeaders() const { |
| // The header looks like |
| // Content-Range: bytes */<content_length> |
| // for example: |
| // Content-Range: bytes */13851821 |
| DCHECK_GE(content_length_, 0); |
| |
| std::vector<std::string> headers; |
| headers.push_back( |
| std::string(kUploadContentRange) + "*/" + |
| base::Int64ToString(content_length_)); |
| return headers; |
| } |
| |
| //============================ DownloadFileRequestBase ========================= |
| |
| DownloadFileRequestBase::DownloadFileRequestBase( |
| RequestSender* sender, |
| const DownloadActionCallback& download_action_callback, |
| const GetContentCallback& get_content_callback, |
| const ProgressCallback& progress_callback, |
| const GURL& download_url, |
| const base::FilePath& output_file_path) |
| : UrlFetchRequestBase(sender), |
| download_action_callback_(download_action_callback), |
| get_content_callback_(get_content_callback), |
| progress_callback_(progress_callback), |
| download_url_(download_url), |
| output_file_path_(output_file_path) { |
| DCHECK(!download_action_callback_.is_null()); |
| DCHECK(!output_file_path_.empty()); |
| // get_content_callback may be null. |
| } |
| |
| DownloadFileRequestBase::~DownloadFileRequestBase() {} |
| |
| // Overridden from UrlFetchRequestBase. |
| GURL DownloadFileRequestBase::GetURL() const { |
| return download_url_; |
| } |
| |
| void DownloadFileRequestBase::GetOutputFilePath( |
| base::FilePath* local_file_path, |
| GetContentCallback* get_content_callback) { |
| // Configure so that the downloaded content is saved to |output_file_path_|. |
| *local_file_path = output_file_path_; |
| *get_content_callback = get_content_callback_; |
| } |
| |
| void DownloadFileRequestBase::OnURLFetchDownloadProgress( |
| const URLFetcher* source, |
| int64 current, |
| int64 total) { |
| if (!progress_callback_.is_null()) |
| progress_callback_.Run(current, total); |
| } |
| |
| void DownloadFileRequestBase::ProcessURLFetchResults(const URLFetcher* source) { |
| GDataErrorCode code = GetErrorCode(); |
| |
| // Take over the ownership of the the downloaded temp file. |
| base::FilePath temp_file; |
| if (code == HTTP_SUCCESS) { |
| response_writer()->DisownFile(); |
| temp_file = output_file_path_; |
| } |
| |
| download_action_callback_.Run(code, temp_file); |
| OnProcessURLFetchResultsComplete(); |
| } |
| |
| void DownloadFileRequestBase::RunCallbackOnPrematureFailure( |
| GDataErrorCode code) { |
| download_action_callback_.Run(code, base::FilePath()); |
| } |
| |
| } // namespace google_apis |