// 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/google_apis/gdata_operations.h" #include "base/string_number_conversions.h" #include "base/stringprintf.h" #include "base/values.h" #include "chrome/browser/google_apis/gdata_util.h" #include "chrome/browser/google_apis/gdata_wapi_parser.h" #include "chrome/common/net/url_util.h" #include "content/public/browser/browser_thread.h" #include "net/base/escape.h" #include "net/http/http_util.h" #include "third_party/libxml/chromium/libxml_utils.h" using net::URLFetcher; namespace { // etag matching header. const char kIfMatchAllHeader[] = "If-Match: *"; const char kIfMatchHeaderFormat[] = "If-Match: %s"; // URL requesting documents list that belong to the authenticated user only // (handled with '/-/mine' part). const char kGetDocumentListURLForAllDocuments[] = "https://docs.google.com/feeds/default/private/full/-/mine"; // URL requesting documents list in a particular directory specified by "%s" // that belong to the authenticated user only (handled with '/-/mine' part). const char kGetDocumentListURLForDirectoryFormat[] = "https://docs.google.com/feeds/default/private/full/%s/contents/-/mine"; // URL requesting documents list of changes to documents collections. const char kGetChangesListURL[] = "https://docs.google.com/feeds/default/private/changes"; // Root document list url. const char kDocumentListRootURL[] = "https://docs.google.com/feeds/default/private/full"; // URL requesting single document entry whose resource id is specified by "%s". const char kGetDocumentEntryURLFormat[] = "https://docs.google.com/feeds/default/private/full/%s"; // Metadata feed with things like user quota. const char kAccountMetadataURL[] = "https://docs.google.com/feeds/metadata/default"; // URL requesting all contact groups. const char kGetContactGroupsURL[] = "https://www.google.com/m8/feeds/groups/default/full?alt=json"; // URL requesting all contacts. // TODO(derat): Per https://goo.gl/AufHP, "The feed may not contain all of the // user's contacts, because there's a default limit on the number of results // returned." Decide if 10000 is reasonable or not. const char kGetContactsURL[] = "https://www.google.com/m8/feeds/contacts/default/full" "?alt=json&showdeleted=true&max-results=10000"; // Query parameter optionally appended to |kGetContactsURL| to return contacts // from a specific group (as opposed to all contacts). const char kGetContactsGroupParam[] = "group"; // Query parameter optionally appended to |kGetContactsURL| to return only // recently-updated contacts. const char kGetContactsUpdatedMinParam[] = "updated-min"; const char kUploadContentRange[] = "Content-Range: bytes "; const char kUploadContentType[] = "X-Upload-Content-Type: "; const char kUploadContentLength[] = "X-Upload-Content-Length: "; #ifndef NDEBUG // Use smaller 'page' size while debugging to ensure we hit feed reload // almost always. Be careful not to use something too small on account that // have many items because server side 503 error might kick in. const int kMaxDocumentsPerFeed = 500; const int kMaxDocumentsPerSearchFeed = 50; #else const int kMaxDocumentsPerFeed = 500; const int kMaxDocumentsPerSearchFeed = 50; #endif const char kFeedField[] = "feed"; // Templates for file uploading. const char kUploadParamConvertKey[] = "convert"; const char kUploadParamConvertValue[] = "false"; const char kUploadResponseLocation[] = "location"; const char kUploadResponseRange[] = "range"; // Adds additional parameters for API version, output content type and to show // folders in the feed are added to document feed URLs. GURL AddStandardUrlParams(const GURL& url) { GURL result = chrome_common_net::AppendOrReplaceQueryParameter(url, "v", "3"); result = chrome_common_net::AppendOrReplaceQueryParameter(result, "alt", "json"); return result; } // Adds additional parameters to metadata feed to include installed 3rd party // applications. GURL AddMetadataUrlParams(const GURL& url) { GURL result = AddStandardUrlParams(url); result = chrome_common_net::AppendOrReplaceQueryParameter( result, "include-installed-apps", "true"); return result; } // Adds additional parameters for API version, output content type and to show // folders in the feed are added to document feed URLs. GURL AddFeedUrlParams(const GURL& url, int num_items_to_fetch, int changestamp, const std::string& search_string) { GURL result = AddStandardUrlParams(url); result = chrome_common_net::AppendOrReplaceQueryParameter( result, "showfolders", "true"); result = chrome_common_net::AppendOrReplaceQueryParameter( result, "max-results", base::StringPrintf("%d", num_items_to_fetch)); result = chrome_common_net::AppendOrReplaceQueryParameter( result, "include-installed-apps", "true"); if (changestamp) { result = chrome_common_net::AppendQueryParameter( result, "start-index", base::StringPrintf("%d", changestamp)); } if (!search_string.empty()) { result = chrome_common_net::AppendOrReplaceQueryParameter( result, "q", search_string); } return result; } // Formats a URL for getting document list. If |directory_resource_id| is // empty, returns a URL for fetching all documents. If it's given, returns a // URL for fetching documents in a particular directory. GURL FormatDocumentListURL(const std::string& directory_resource_id) { if (directory_resource_id.empty()) return GURL(kGetDocumentListURLForAllDocuments); return GURL(base::StringPrintf(kGetDocumentListURLForDirectoryFormat, net::EscapePath( directory_resource_id).c_str())); } } // namespace namespace gdata { //============================ Structs =========================== ResumeUploadResponse::ResumeUploadResponse(GDataErrorCode code, int64 start_range_received, int64 end_range_received) : code(code), start_range_received(start_range_received), end_range_received(end_range_received) { } ResumeUploadResponse::~ResumeUploadResponse() { } InitiateUploadParams::InitiateUploadParams( UploadMode upload_mode, const std::string& title, const std::string& content_type, int64 content_length, const GURL& upload_location, const FilePath& virtual_path) : upload_mode(upload_mode), title(title), content_type(content_type), content_length(content_length), upload_location(upload_location), virtual_path(virtual_path) { } InitiateUploadParams::~InitiateUploadParams() { } ResumeUploadParams::ResumeUploadParams( UploadMode upload_mode, int64 start_range, int64 end_range, int64 content_length, const std::string& content_type, scoped_refptr buf, const GURL& upload_location, const FilePath& virtual_path) : upload_mode(upload_mode), start_range(start_range), end_range(end_range), content_length(content_length), content_type(content_type), buf(buf), upload_location(upload_location), virtual_path(virtual_path) { } ResumeUploadParams::~ResumeUploadParams() { } //============================ GetDocumentsOperation =========================== GetDocumentsOperation::GetDocumentsOperation( OperationRegistry* registry, const GURL& url, int start_changestamp, const std::string& search_string, const std::string& directory_resource_id, const GetDataCallback& callback) : GetDataOperation(registry, callback), override_url_(url), start_changestamp_(start_changestamp), search_string_(search_string), directory_resource_id_(directory_resource_id) { } GetDocumentsOperation::~GetDocumentsOperation() {} GURL GetDocumentsOperation::GetURL() const { int max_docs = search_string_.empty() ? kMaxDocumentsPerFeed : kMaxDocumentsPerSearchFeed; if (!override_url_.is_empty()) return AddFeedUrlParams(override_url_, max_docs, 0, search_string_); if (start_changestamp_ == 0) { return AddFeedUrlParams(FormatDocumentListURL(directory_resource_id_), max_docs, 0, search_string_); } return AddFeedUrlParams(GURL(kGetChangesListURL), kMaxDocumentsPerFeed, start_changestamp_, std::string()); } //============================ GetDocumentEntryOperation ======================= GetDocumentEntryOperation::GetDocumentEntryOperation( OperationRegistry* registry, const std::string& resource_id, const GetDataCallback& callback) : GetDataOperation(registry, callback), resource_id_(resource_id) { } GetDocumentEntryOperation::~GetDocumentEntryOperation() {} GURL GetDocumentEntryOperation::GetURL() const { GURL result = GURL(base::StringPrintf(kGetDocumentEntryURLFormat, net::EscapePath(resource_id_).c_str())); return AddStandardUrlParams(result); } //========================= GetAccountMetadataOperation ======================== GetAccountMetadataOperation::GetAccountMetadataOperation( OperationRegistry* registry, const GetDataCallback& callback) : GetDataOperation(registry, callback) { } GetAccountMetadataOperation::~GetAccountMetadataOperation() {} GURL GetAccountMetadataOperation::GetURL() const { return AddMetadataUrlParams(GURL(kAccountMetadataURL)); } //============================ DownloadFileOperation =========================== DownloadFileOperation::DownloadFileOperation( OperationRegistry* registry, const DownloadActionCallback& download_action_callback, const GetContentCallback& get_content_callback, const GURL& document_url, const FilePath& virtual_path, const FilePath& output_file_path) : UrlFetchOperationBase(registry, OPERATION_DOWNLOAD, virtual_path), download_action_callback_(download_action_callback), get_content_callback_(get_content_callback), document_url_(document_url) { // Make sure we download the content into a temp file. if (output_file_path.empty()) save_temp_file_ = true; else output_file_path_ = output_file_path; } DownloadFileOperation::~DownloadFileOperation() {} // Overridden from UrlFetchOperationBase. GURL DownloadFileOperation::GetURL() const { return document_url_; } void DownloadFileOperation::OnURLFetchDownloadProgress(const URLFetcher* source, int64 current, int64 total) { NotifyProgress(current, total); } bool DownloadFileOperation::ShouldSendDownloadData() { return !get_content_callback_.is_null(); } void DownloadFileOperation::OnURLFetchDownloadData( const URLFetcher* source, scoped_ptr download_data) { if (!get_content_callback_.is_null()) get_content_callback_.Run(HTTP_SUCCESS, download_data.Pass()); } void DownloadFileOperation::ProcessURLFetchResults(const URLFetcher* source) { GDataErrorCode code = GetErrorCode(source); // Take over the ownership of the the downloaded temp file. FilePath temp_file; if (code == HTTP_SUCCESS && !source->GetResponseAsFilePath(true, // take_ownership &temp_file)) { code = GDATA_FILE_ERROR; } if (!download_action_callback_.is_null()) download_action_callback_.Run(code, document_url_, temp_file); OnProcessURLFetchResultsComplete(code == HTTP_SUCCESS); } void DownloadFileOperation::RunCallbackOnPrematureFailure(GDataErrorCode code) { if (!download_action_callback_.is_null()) download_action_callback_.Run(code, document_url_, FilePath()); } //=========================== DeleteDocumentOperation ========================== DeleteDocumentOperation::DeleteDocumentOperation( OperationRegistry* registry, const EntryActionCallback& callback, const GURL& document_url) : EntryActionOperation(registry, callback, document_url) { } DeleteDocumentOperation::~DeleteDocumentOperation() {} GURL DeleteDocumentOperation::GetURL() const { return AddStandardUrlParams(document_url()); } URLFetcher::RequestType DeleteDocumentOperation::GetRequestType() const { return URLFetcher::DELETE_REQUEST; } std::vector DeleteDocumentOperation::GetExtraRequestHeaders() const { std::vector headers; headers.push_back(kIfMatchAllHeader); return headers; } //========================== CreateDirectoryOperation ========================== CreateDirectoryOperation::CreateDirectoryOperation( OperationRegistry* registry, const GetDataCallback& callback, const GURL& parent_content_url, const FilePath::StringType& directory_name) : GetDataOperation(registry, callback), parent_content_url_(parent_content_url), directory_name_(directory_name) { } CreateDirectoryOperation::~CreateDirectoryOperation() {} GURL CreateDirectoryOperation::GetURL() const { if (!parent_content_url_.is_empty()) return AddStandardUrlParams(parent_content_url_); return AddStandardUrlParams(GURL(kDocumentListRootURL)); } URLFetcher::RequestType CreateDirectoryOperation::GetRequestType() const { return URLFetcher::POST; } bool CreateDirectoryOperation::GetContentData(std::string* upload_content_type, std::string* upload_content) { upload_content_type->assign("application/atom+xml"); XmlWriter xml_writer; xml_writer.StartWriting(); xml_writer.StartElement("entry"); xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom"); xml_writer.StartElement("category"); xml_writer.AddAttribute("scheme", "http://schemas.google.com/g/2005#kind"); xml_writer.AddAttribute("term", "http://schemas.google.com/docs/2007#folder"); xml_writer.EndElement(); // Ends "category" element. xml_writer.WriteElement("title", FilePath(directory_name_).AsUTF8Unsafe()); xml_writer.EndElement(); // Ends "entry" element. xml_writer.StopWriting(); upload_content->assign(xml_writer.GetWrittenString()); DVLOG(1) << "CreateDirectory data: " << *upload_content_type << ", [" << *upload_content << "]"; return true; } //============================ CopyDocumentOperation =========================== CopyDocumentOperation::CopyDocumentOperation( OperationRegistry* registry, const GetDataCallback& callback, const std::string& resource_id, const FilePath::StringType& new_name) : GetDataOperation(registry, callback), resource_id_(resource_id), new_name_(new_name) { } CopyDocumentOperation::~CopyDocumentOperation() {} URLFetcher::RequestType CopyDocumentOperation::GetRequestType() const { return URLFetcher::POST; } GURL CopyDocumentOperation::GetURL() const { return AddStandardUrlParams(GURL(kDocumentListRootURL)); } bool CopyDocumentOperation::GetContentData(std::string* upload_content_type, std::string* upload_content) { upload_content_type->assign("application/atom+xml"); XmlWriter xml_writer; xml_writer.StartWriting(); xml_writer.StartElement("entry"); xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom"); xml_writer.WriteElement("id", resource_id_); xml_writer.WriteElement("title", FilePath(new_name_).AsUTF8Unsafe()); xml_writer.EndElement(); // Ends "entry" element. xml_writer.StopWriting(); upload_content->assign(xml_writer.GetWrittenString()); DVLOG(1) << "CopyDocumentOperation data: " << *upload_content_type << ", [" << *upload_content << "]"; return true; } //=========================== RenameResourceOperation ========================== RenameResourceOperation::RenameResourceOperation( OperationRegistry* registry, const EntryActionCallback& callback, const GURL& document_url, const FilePath::StringType& new_name) : EntryActionOperation(registry, callback, document_url), new_name_(new_name) { } RenameResourceOperation::~RenameResourceOperation() {} URLFetcher::RequestType RenameResourceOperation::GetRequestType() const { return URLFetcher::PUT; } std::vector RenameResourceOperation::GetExtraRequestHeaders() const { std::vector headers; headers.push_back(kIfMatchAllHeader); return headers; } GURL RenameResourceOperation::GetURL() const { return AddStandardUrlParams(document_url()); } bool RenameResourceOperation::GetContentData(std::string* upload_content_type, std::string* upload_content) { upload_content_type->assign("application/atom+xml"); XmlWriter xml_writer; xml_writer.StartWriting(); xml_writer.StartElement("entry"); xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom"); xml_writer.WriteElement("title", FilePath(new_name_).AsUTF8Unsafe()); xml_writer.EndElement(); // Ends "entry" element. xml_writer.StopWriting(); upload_content->assign(xml_writer.GetWrittenString()); DVLOG(1) << "RenameResourceOperation data: " << *upload_content_type << ", [" << *upload_content << "]"; return true; } //=========================== AuthorizeAppOperation ========================== AuthorizeAppsOperation::AuthorizeAppsOperation( OperationRegistry* registry, const GetDataCallback& callback, const GURL& document_url, const std::string& app_id) : GetDataOperation(registry, callback), app_id_(app_id), document_url_(document_url) { } AuthorizeAppsOperation::~AuthorizeAppsOperation() {} URLFetcher::RequestType AuthorizeAppsOperation::GetRequestType() const { return URLFetcher::PUT; } std::vector AuthorizeAppsOperation::GetExtraRequestHeaders() const { std::vector headers; headers.push_back(kIfMatchAllHeader); return headers; } void AuthorizeAppsOperation::ProcessURLFetchResults(const URLFetcher* source) { std::string data; source->GetResponseAsString(&data); GetDataOperation::ProcessURLFetchResults(source); } bool AuthorizeAppsOperation::GetContentData(std::string* upload_content_type, std::string* upload_content) { upload_content_type->assign("application/atom+xml"); XmlWriter xml_writer; xml_writer.StartWriting(); xml_writer.StartElement("entry"); xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom"); xml_writer.AddAttribute("xmlns:docs", "http://schemas.google.com/docs/2007"); xml_writer.WriteElement("docs:authorizedApp", app_id_); xml_writer.EndElement(); // Ends "entry" element. xml_writer.StopWriting(); upload_content->assign(xml_writer.GetWrittenString()); DVLOG(1) << "AuthorizeAppOperation data: " << *upload_content_type << ", [" << *upload_content << "]"; return true; } void AuthorizeAppsOperation::ParseResponse( GDataErrorCode fetch_error_code, const std::string& data) { // Parse entry XML. XmlReader xml_reader; scoped_ptr entry; if (xml_reader.Load(data)) { while (xml_reader.Read()) { if (xml_reader.NodeName() == DocumentEntry::GetEntryNodeName()) { entry.reset(DocumentEntry::CreateFromXml(&xml_reader)); break; } } } // From the response, we create a list of the links returned, since those // are the only things we are interested in. scoped_ptr link_list(new ListValue); const ScopedVector& feed_links = entry->links(); for (ScopedVector::const_iterator iter = feed_links.begin(); iter != feed_links.end(); ++iter) { if ((*iter)->type() == Link::LINK_OPEN_WITH) { base::DictionaryValue* link = new DictionaryValue; link->SetString(std::string("href"), (*iter)->href().spec()); link->SetString(std::string("mime_type"), (*iter)->mime_type()); link->SetString(std::string("title"), (*iter)->title()); link->SetString(std::string("app_id"), (*iter)->app_id()); link_list->Append(link); } } RunCallback(fetch_error_code, link_list.PassAs()); const bool success = true; OnProcessURLFetchResultsComplete(success); } GURL AuthorizeAppsOperation::GetURL() const { return document_url_; } //======================= AddResourceToDirectoryOperation ====================== AddResourceToDirectoryOperation::AddResourceToDirectoryOperation( OperationRegistry* registry, const EntryActionCallback& callback, const GURL& parent_content_url, const GURL& document_url) : EntryActionOperation(registry, callback, document_url), parent_content_url_(parent_content_url) { } AddResourceToDirectoryOperation::~AddResourceToDirectoryOperation() {} GURL AddResourceToDirectoryOperation::GetURL() const { if (!parent_content_url_.is_empty()) return AddStandardUrlParams(parent_content_url_); return AddStandardUrlParams(GURL(kDocumentListRootURL)); } URLFetcher::RequestType AddResourceToDirectoryOperation::GetRequestType() const { return URLFetcher::POST; } bool AddResourceToDirectoryOperation::GetContentData( std::string* upload_content_type, std::string* upload_content) { upload_content_type->assign("application/atom+xml"); XmlWriter xml_writer; xml_writer.StartWriting(); xml_writer.StartElement("entry"); xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom"); xml_writer.WriteElement("id", document_url().spec()); xml_writer.EndElement(); // Ends "entry" element. xml_writer.StopWriting(); upload_content->assign(xml_writer.GetWrittenString()); DVLOG(1) << "AddResourceToDirectoryOperation data: " << *upload_content_type << ", [" << *upload_content << "]"; return true; } //==================== RemoveResourceFromDirectoryOperation ==================== RemoveResourceFromDirectoryOperation::RemoveResourceFromDirectoryOperation( OperationRegistry* registry, const EntryActionCallback& callback, const GURL& parent_content_url, const GURL& document_url, const std::string& document_resource_id) : EntryActionOperation(registry, callback, document_url), resource_id_(document_resource_id), parent_content_url_(parent_content_url) { } RemoveResourceFromDirectoryOperation::~RemoveResourceFromDirectoryOperation() { } GURL RemoveResourceFromDirectoryOperation::GetURL() const { std::string escaped_resource_id = net::EscapePath(resource_id_); GURL edit_url(base::StringPrintf("%s/%s", parent_content_url_.spec().c_str(), escaped_resource_id.c_str())); return AddStandardUrlParams(edit_url); } URLFetcher::RequestType RemoveResourceFromDirectoryOperation::GetRequestType() const { return URLFetcher::DELETE_REQUEST; } std::vector RemoveResourceFromDirectoryOperation::GetExtraRequestHeaders() const { std::vector headers; headers.push_back(kIfMatchAllHeader); return headers; } //=========================== InitiateUploadOperation ========================== InitiateUploadOperation::InitiateUploadOperation( OperationRegistry* registry, const InitiateUploadCallback& callback, const InitiateUploadParams& params) : UrlFetchOperationBase(registry, OPERATION_UPLOAD, params.virtual_path), callback_(callback), params_(params), initiate_upload_url_(chrome_common_net::AppendOrReplaceQueryParameter( params.upload_location, kUploadParamConvertKey, kUploadParamConvertValue)) { } InitiateUploadOperation::~InitiateUploadOperation() {} GURL InitiateUploadOperation::GetURL() const { return initiate_upload_url_; } void InitiateUploadOperation::ProcessURLFetchResults( const URLFetcher* source) { GDataErrorCode code = GetErrorCode(source); std::string upload_location; if (code == HTTP_SUCCESS) { // Retrieve value of the first "Location" header. source->GetResponseHeaders()->EnumerateHeader(NULL, kUploadResponseLocation, &upload_location); } VLOG(1) << "Got response for [" << params_.title << "]: code=" << code << ", location=[" << upload_location << "]"; if (!callback_.is_null()) callback_.Run(code, GURL(upload_location)); OnProcessURLFetchResultsComplete(code == HTTP_SUCCESS); } void InitiateUploadOperation::NotifySuccessToOperationRegistry() { NotifySuspend(); } void InitiateUploadOperation::RunCallbackOnPrematureFailure( GDataErrorCode code) { if (!callback_.is_null()) callback_.Run(code, GURL()); } URLFetcher::RequestType InitiateUploadOperation::GetRequestType() const { if (params_.upload_mode == UPLOAD_NEW_FILE) return URLFetcher::POST; DCHECK_EQ(UPLOAD_EXISTING_FILE, params_.upload_mode); return URLFetcher::PUT; } std::vector InitiateUploadOperation::GetExtraRequestHeaders() const { std::vector headers; if (!params_.content_type.empty()) headers.push_back(kUploadContentType + params_.content_type); headers.push_back( kUploadContentLength + base::Int64ToString(params_.content_length)); if (params_.upload_mode == UPLOAD_EXISTING_FILE) headers.push_back("If-Match: *"); return headers; } bool InitiateUploadOperation::GetContentData(std::string* upload_content_type, std::string* upload_content) { if (params_.upload_mode == UPLOAD_EXISTING_FILE) { // When uploading an existing file, the body is empty as we don't modify // the metadata. *upload_content = ""; // Even though the body is empty, Content-Type should be set to // "text/plain". Otherwise, the server won't accept. *upload_content_type = "text/plain"; return true; } DCHECK_EQ(UPLOAD_NEW_FILE, params_.upload_mode); upload_content_type->assign("application/atom+xml"); XmlWriter xml_writer; xml_writer.StartWriting(); xml_writer.StartElement("entry"); xml_writer.AddAttribute("xmlns", "http://www.w3.org/2005/Atom"); xml_writer.AddAttribute("xmlns:docs", "http://schemas.google.com/docs/2007"); xml_writer.WriteElement("title", params_.title); xml_writer.EndElement(); // Ends "entry" element. xml_writer.StopWriting(); upload_content->assign(xml_writer.GetWrittenString()); DVLOG(1) << "Upload data: " << *upload_content_type << ", [" << *upload_content << "]"; return true; } //============================ ResumeUploadOperation =========================== ResumeUploadOperation::ResumeUploadOperation( OperationRegistry* registry, const ResumeUploadCallback& callback, const ResumeUploadParams& params) : UrlFetchOperationBase(registry, OPERATION_UPLOAD, params.virtual_path), callback_(callback), params_(params), last_chunk_completed_(false) { } ResumeUploadOperation::~ResumeUploadOperation() {} GURL ResumeUploadOperation::GetURL() const { return params_.upload_location; } void ResumeUploadOperation::ProcessURLFetchResults(const URLFetcher* source) { GDataErrorCode code = GetErrorCode(source); net::HttpResponseHeaders* hdrs = source->GetResponseHeaders(); int64 start_range_received = -1; int64 end_range_received = -1; scoped_ptr entry; if (code == HTTP_RESUME_INCOMPLETE) { // Retrieve value of the first "Range" header. std::string range_received; hdrs->EnumerateHeader(NULL, kUploadResponseRange, &range_received); if (!range_received.empty()) { // Parse the range header. std::vector ranges; if (net::HttpUtil::ParseRangeHeader(range_received, &ranges) && !ranges.empty() ) { // We only care about the first start-end pair in the range. start_range_received = ranges[0].first_byte_position(); end_range_received = ranges[0].last_byte_position(); } } DVLOG(1) << "Got response for [" << params_.virtual_path.value() << "]: code=" << code << ", range_hdr=[" << range_received << "], range_parsed=" << start_range_received << "," << end_range_received; } else { // There might be explanation of unexpected error code in response. std::string response_content; source->GetResponseAsString(&response_content); DVLOG(1) << "Got response for [" << params_.virtual_path.value() << "]: code=" << code << ", content=[\n" << response_content << "\n]"; // Parse entry XML. XmlReader xml_reader; if (xml_reader.Load(response_content)) { while (xml_reader.Read()) { if (xml_reader.NodeName() == DocumentEntry::GetEntryNodeName()) { entry.reset(DocumentEntry::CreateFromXml(&xml_reader)); break; } } } if (!entry.get()) LOG(WARNING) << "Invalid entry received on upload:\n" << response_content; } if (!callback_.is_null()) { callback_.Run(ResumeUploadResponse(code, start_range_received, end_range_received), entry.Pass()); } // For a new file, HTTP_CREATED is returned. // For an existing file, HTTP_SUCCESS is returned. if ((params_.upload_mode == UPLOAD_NEW_FILE && code == HTTP_CREATED) || (params_.upload_mode == UPLOAD_EXISTING_FILE && code == HTTP_SUCCESS)) { last_chunk_completed_ = true; } OnProcessURLFetchResultsComplete( last_chunk_completed_ || code == HTTP_RESUME_INCOMPLETE); } void ResumeUploadOperation::NotifyStartToOperationRegistry() { NotifyResume(); } void ResumeUploadOperation::NotifySuccessToOperationRegistry() { if (last_chunk_completed_) NotifyFinish(OPERATION_COMPLETED); else NotifySuspend(); } void ResumeUploadOperation::RunCallbackOnPrematureFailure(GDataErrorCode code) { scoped_ptr entry; if (!callback_.is_null()) callback_.Run(ResumeUploadResponse(code, 0, 0), entry.Pass()); } URLFetcher::RequestType ResumeUploadOperation::GetRequestType() const { return URLFetcher::PUT; } std::vector ResumeUploadOperation::GetExtraRequestHeaders() const { if (params_.content_length == 0) { // For uploading an empty document, just PUT an empty content. DCHECK_EQ(params_.start_range, 0); DCHECK_EQ(params_.end_range, -1); return std::vector(); } // The header looks like // Content-Range: bytes -/ // for example: // Content-Range: bytes 7864320-8388607/13851821 // Use * for unknown/streaming content length. DCHECK_GE(params_.start_range, 0); DCHECK_GE(params_.end_range, 0); DCHECK_GE(params_.content_length, -1); std::vector headers; headers.push_back( std::string(kUploadContentRange) + base::Int64ToString(params_.start_range) + "-" + base::Int64ToString(params_.end_range) + "/" + (params_.content_length == -1 ? "*" : base::Int64ToString(params_.content_length))); return headers; } bool ResumeUploadOperation::GetContentData(std::string* upload_content_type, std::string* upload_content) { *upload_content_type = params_.content_type; *upload_content = std::string(params_.buf->data(), params_.end_range - params_.start_range + 1); return true; } void ResumeUploadOperation::OnURLFetchUploadProgress( const URLFetcher* source, int64 current, int64 total) { // Adjust the progress values according to the range currently uploaded. NotifyProgress(params_.start_range + current, params_.content_length); } //========================== GetContactGroupsOperation ========================= GetContactGroupsOperation::GetContactGroupsOperation( OperationRegistry* registry, const GetDataCallback& callback) : GetDataOperation(registry, callback) { } GetContactGroupsOperation::~GetContactGroupsOperation() {} GURL GetContactGroupsOperation::GetURL() const { return !feed_url_for_testing_.is_empty() ? feed_url_for_testing_ : GURL(kGetContactGroupsURL); } //============================ GetContactsOperation ============================ GetContactsOperation::GetContactsOperation(OperationRegistry* registry, const std::string& group_id, const base::Time& min_update_time, const GetDataCallback& callback) : GetDataOperation(registry, callback), group_id_(group_id), min_update_time_(min_update_time) { } GetContactsOperation::~GetContactsOperation() {} GURL GetContactsOperation::GetURL() const { if (!feed_url_for_testing_.is_empty()) return GURL(feed_url_for_testing_); GURL url(kGetContactsURL); if (!group_id_.empty()) { url = chrome_common_net::AppendQueryParameter( url, kGetContactsGroupParam, group_id_); } if (!min_update_time_.is_null()) { std::string time_rfc3339 = util::FormatTimeAsString(min_update_time_); url = chrome_common_net::AppendQueryParameter( url, kGetContactsUpdatedMinParam, time_rfc3339); } return url; } //========================== GetContactPhotoOperation ========================== GetContactPhotoOperation::GetContactPhotoOperation( OperationRegistry* registry, const GURL& photo_url, const GetContentCallback& callback) : UrlFetchOperationBase(registry), photo_url_(photo_url), callback_(callback) { } GetContactPhotoOperation::~GetContactPhotoOperation() {} GURL GetContactPhotoOperation::GetURL() const { return photo_url_; } void GetContactPhotoOperation::ProcessURLFetchResults( const net::URLFetcher* source) { GDataErrorCode code = static_cast(source->GetResponseCode()); scoped_ptr data(new std::string); source->GetResponseAsString(data.get()); callback_.Run(code, data.Pass()); OnProcessURLFetchResultsComplete(code == HTTP_SUCCESS); } void GetContactPhotoOperation::RunCallbackOnPrematureFailure( GDataErrorCode code) { scoped_ptr data(new std::string); callback_.Run(code, data.Pass()); } } // namespace gdata