| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "google_apis/drive/drive_api_requests.h" |
| |
| #include <stddef.h> |
| |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/json/json_writer.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/values.h" |
| #include "google_apis/common/base_requests.h" |
| #include "google_apis/common/request_sender.h" |
| #include "google_apis/common/time_util.h" |
| #include "google_apis/drive/request_util.h" |
| #include "net/base/url_util.h" |
| #include "net/http/http_response_headers.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| |
| namespace google_apis::drive { |
| namespace { |
| |
| // Format of one request in batch uploading request. |
| const char kBatchUploadRequestFormat[] = |
| "%s %s HTTP/1.1\n" |
| "Host: %s\n" |
| "X-Goog-Upload-Protocol: multipart\n" |
| "Content-Type: %s\n" |
| "\n"; |
| |
| // Request header for specifying batch upload. |
| const char kBatchUploadHeader[] = "X-Goog-Upload-Protocol: batch"; |
| |
| // Content type of HTTP request. |
| const char kHttpContentType[] = "application/http"; |
| |
| // Break line in HTTP message. |
| const char kHttpBr[] = "\r\n"; |
| |
| // Mime type of multipart mixed. |
| const char kMultipartMixedMimeTypePrefix[] = "multipart/mixed; boundary="; |
| |
| // Parses the JSON value to FileResource instance and runs |callback| on the |
| // UI thread once parsing is done. |
| // This is customized version of ParseJsonAndRun defined above to adapt the |
| // remaining response type. |
| void ParseFileResourceWithUploadRangeAndRun( |
| UploadRangeCallback callback, |
| const UploadRangeResponse& response, |
| std::unique_ptr<base::Value> value) { |
| DCHECK(!callback.is_null()); |
| |
| std::unique_ptr<FileResource> file_resource; |
| if (value) { |
| file_resource = FileResource::CreateFrom(*value); |
| if (!file_resource) { |
| std::move(callback).Run( |
| UploadRangeResponse(PARSE_ERROR, response.start_position_received, |
| response.end_position_received), |
| std::unique_ptr<FileResource>()); |
| return; |
| } |
| } |
| |
| std::move(callback).Run(response, std::move(file_resource)); |
| } |
| |
| // Attaches |properties| to the |request_body| if |properties| is not empty. |
| void AttachProperties(const Properties& properties, |
| base::Value::Dict& request_body) { |
| if (properties.empty()) |
| return; |
| |
| base::Value::List properties_value; |
| for (const auto& property : properties) { |
| base::Value::Dict property_value; |
| std::string visibility_as_string; |
| switch (property.visibility()) { |
| case Property::VISIBILITY_PRIVATE: |
| visibility_as_string = "PRIVATE"; |
| break; |
| case Property::VISIBILITY_PUBLIC: |
| visibility_as_string = "PUBLIC"; |
| break; |
| } |
| property_value.Set("visibility", visibility_as_string); |
| property_value.Set("key", property.key()); |
| property_value.Set("value", property.value()); |
| properties_value.Append(std::move(property_value)); |
| } |
| request_body.Set("properties", base::Value(std::move(properties_value))); |
| } |
| |
| // Creates metadata JSON string for multipart uploading. |
| // All the values are optional. If the value is empty or null, the value does |
| // not appear in the metadata. |
| std::string CreateMultipartUploadMetadataJson( |
| const std::string& title, |
| const std::string& parent_resource_id, |
| const base::Time& modified_date, |
| const base::Time& last_viewed_by_me_date, |
| const Properties& properties) { |
| base::Value::Dict root; |
| if (!title.empty()) |
| root.Set("title", title); |
| |
| // Fill parent link. |
| if (!parent_resource_id.empty()) { |
| base::Value::List parents; |
| parents.Append(google_apis::util::CreateParentValue(parent_resource_id)); |
| root.Set("parents", base::Value(std::move(parents))); |
| } |
| |
| if (!modified_date.is_null()) { |
| root.Set("modifiedDate", |
| google_apis::util::FormatTimeAsString(modified_date)); |
| } |
| |
| if (!last_viewed_by_me_date.is_null()) { |
| root.Set("lastViewedByMeDate", |
| google_apis::util::FormatTimeAsString(last_viewed_by_me_date)); |
| } |
| |
| AttachProperties(properties, root); |
| std::string json_string; |
| base::JSONWriter::Write(root, &json_string); |
| return json_string; |
| } |
| |
| } // namespace |
| |
| MultipartHttpResponse::MultipartHttpResponse() = default; |
| |
| MultipartHttpResponse::~MultipartHttpResponse() = default; |
| |
| // The |response| must be multipart/mixed format that contains child HTTP |
| // response of drive batch request. |
| // https://www.ietf.org/rfc/rfc2046.txt |
| // |
| // It looks like: |
| // --Boundary |
| // Content-type: application/http |
| // |
| // HTTP/1.1 200 OK |
| // Header of child response |
| // |
| // Body of child response |
| // --Boundary |
| // Content-type: application/http |
| // |
| // HTTP/1.1 404 Not Found |
| // Header of child response |
| // |
| // Body of child response |
| // --Boundary-- |
| bool ParseMultipartResponse(const std::string& content_type, |
| const std::string& response, |
| std::vector<MultipartHttpResponse>* parts) { |
| if (response.empty()) |
| return false; |
| |
| std::string_view content_type_piece(content_type); |
| if (!base::StartsWith(content_type_piece, kMultipartMixedMimeTypePrefix)) { |
| return false; |
| } |
| content_type_piece.remove_prefix( |
| std::string_view(kMultipartMixedMimeTypePrefix).size()); |
| |
| if (content_type_piece.empty()) |
| return false; |
| if (content_type_piece[0] == '"') { |
| if (content_type_piece.size() <= 2 || content_type_piece.back() != '"') |
| return false; |
| |
| content_type_piece = |
| content_type_piece.substr(1, content_type_piece.size() - 2); |
| } |
| |
| std::string boundary(content_type_piece); |
| const std::string header = "--" + boundary; |
| const std::string terminator = "--" + boundary + "--"; |
| |
| std::vector<std::string_view> lines = base::SplitStringPieceUsingSubstr( |
| response, kHttpBr, base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| |
| enum { |
| STATE_START, |
| STATE_PART_HEADER, |
| STATE_PART_HTTP_STATUS_LINE, |
| STATE_PART_HTTP_HEADER, |
| STATE_PART_HTTP_BODY |
| } state = STATE_START; |
| |
| const std::string kHttpStatusPrefix = "HTTP/1.1 "; |
| std::vector<MultipartHttpResponse> responses; |
| ApiErrorCode code = PARSE_ERROR; |
| std::string body; |
| for (const auto& line : lines) { |
| if (state == STATE_PART_HEADER && line.empty()) { |
| state = STATE_PART_HTTP_STATUS_LINE; |
| continue; |
| } |
| |
| if (state == STATE_PART_HTTP_STATUS_LINE) { |
| if (base::StartsWith(line, kHttpStatusPrefix)) { |
| int int_code; |
| base::StringToInt( |
| line.substr(std::string_view(kHttpStatusPrefix).size()), &int_code); |
| if (int_code > 0) |
| code = static_cast<ApiErrorCode>(int_code); |
| else |
| code = PARSE_ERROR; |
| } else { |
| code = PARSE_ERROR; |
| } |
| state = STATE_PART_HTTP_HEADER; |
| continue; |
| } |
| |
| if (state == STATE_PART_HTTP_HEADER && line.empty()) { |
| state = STATE_PART_HTTP_BODY; |
| body.clear(); |
| continue; |
| } |
| const std::string_view chopped_line = |
| base::TrimString(line, " \t", base::TRIM_TRAILING); |
| const bool is_new_part = chopped_line == header; |
| const bool was_last_part = chopped_line == terminator; |
| if (is_new_part || was_last_part) { |
| switch (state) { |
| case STATE_START: |
| break; |
| case STATE_PART_HEADER: |
| case STATE_PART_HTTP_STATUS_LINE: |
| responses.emplace_back(); |
| responses.back().code = PARSE_ERROR; |
| break; |
| case STATE_PART_HTTP_HEADER: |
| responses.emplace_back(); |
| responses.back().code = code; |
| break; |
| case STATE_PART_HTTP_BODY: |
| // Drop the last kHttpBr. |
| if (!body.empty()) |
| body.resize(body.size() - 2); |
| responses.emplace_back(); |
| responses.back().code = code; |
| responses.back().body.swap(body); |
| break; |
| } |
| if (is_new_part) |
| state = STATE_PART_HEADER; |
| if (was_last_part) |
| break; |
| } else if (state == STATE_PART_HTTP_BODY) { |
| base::StrAppend(&body, {line, kHttpBr}); |
| } |
| } |
| |
| parts->swap(responses); |
| return true; |
| } |
| |
| Property::Property() : visibility_(VISIBILITY_PRIVATE) {} |
| |
| Property::~Property() = default; |
| |
| //============================ DriveApiPartialFieldRequest ==================== |
| |
| DriveApiPartialFieldRequest::DriveApiPartialFieldRequest(RequestSender* sender) |
| : DriveUrlFetchRequestBase(sender, ProgressCallback(), ProgressCallback()) { |
| } |
| |
| DriveApiPartialFieldRequest::~DriveApiPartialFieldRequest() = default; |
| |
| GURL DriveApiPartialFieldRequest::GetURL() const { |
| GURL url = GetURLInternal(); |
| if (!fields_.empty()) |
| url = net::AppendOrReplaceQueryParameter(url, "fields", fields_); |
| return url; |
| } |
| |
| //=============================== FilesGetRequest ============================= |
| |
| FilesGetRequest::FilesGetRequest(RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| FileResourceCallback callback) |
| : DriveApiDataRequest<FileResource>(sender, std::move(callback)), |
| url_generator_(url_generator) {} |
| |
| FilesGetRequest::~FilesGetRequest() = default; |
| |
| GURL FilesGetRequest::GetURLInternal() const { |
| return url_generator_.GetFilesGetUrl(file_id_, embed_origin_); |
| } |
| |
| //============================ FilesInsertRequest ============================ |
| |
| FilesInsertRequest::FilesInsertRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| FileResourceCallback callback) |
| : DriveApiDataRequest<FileResource>(sender, std::move(callback)), |
| url_generator_(url_generator), |
| visibility_(FILE_VISIBILITY_DEFAULT) {} |
| |
| FilesInsertRequest::~FilesInsertRequest() = default; |
| |
| HttpRequestMethod FilesInsertRequest::GetRequestType() const { |
| return HttpRequestMethod::kPost; |
| } |
| |
| bool FilesInsertRequest::GetContentData(std::string* upload_content_type, |
| std::string* upload_content) { |
| *upload_content_type = util::kContentTypeApplicationJson; |
| |
| base::Value::Dict root; |
| |
| if (!last_viewed_by_me_date_.is_null()) { |
| root.Set("lastViewedByMeDate", |
| util::FormatTimeAsString(last_viewed_by_me_date_)); |
| } |
| |
| if (!mime_type_.empty()) |
| root.Set("mimeType", mime_type_); |
| |
| if (!modified_date_.is_null()) |
| root.Set("modifiedDate", util::FormatTimeAsString(modified_date_)); |
| |
| if (!parents_.empty()) { |
| base::Value::List parents_value; |
| for (const std::string& parent_id : parents_) { |
| base::Value::Dict parent; |
| parent.Set("id", parent_id); |
| parents_value.Append(std::move(parent)); |
| } |
| root.Set("parents", base::Value(std::move(parents_value))); |
| } |
| |
| if (!title_.empty()) |
| root.Set("title", title_); |
| |
| AttachProperties(properties_, root); |
| base::JSONWriter::Write(root, upload_content); |
| |
| DVLOG(1) << "FilesInsert data: " << *upload_content_type << ", [" |
| << *upload_content << "]"; |
| return true; |
| } |
| |
| GURL FilesInsertRequest::GetURLInternal() const { |
| return url_generator_.GetFilesInsertUrl( |
| visibility_ == FILE_VISIBILITY_PRIVATE ? "PRIVATE" : ""); |
| } |
| |
| //============================== FilesPatchRequest ============================ |
| |
| FilesPatchRequest::FilesPatchRequest(RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| FileResourceCallback callback) |
| : DriveApiDataRequest<FileResource>(sender, std::move(callback)), |
| url_generator_(url_generator), |
| set_modified_date_(false), |
| update_viewed_date_(true) {} |
| |
| FilesPatchRequest::~FilesPatchRequest() = default; |
| |
| HttpRequestMethod FilesPatchRequest::GetRequestType() const { |
| return HttpRequestMethod::kPatch; |
| } |
| |
| std::vector<std::string> FilesPatchRequest::GetExtraRequestHeaders() const { |
| std::vector<std::string> headers; |
| headers.push_back(util::kIfMatchAllHeader); |
| return headers; |
| } |
| |
| GURL FilesPatchRequest::GetURLInternal() const { |
| return url_generator_.GetFilesPatchUrl(file_id_, set_modified_date_, |
| update_viewed_date_); |
| } |
| |
| bool FilesPatchRequest::GetContentData(std::string* upload_content_type, |
| std::string* upload_content) { |
| if (title_.empty() && modified_date_.is_null() && |
| last_viewed_by_me_date_.is_null() && parents_.empty()) |
| return false; |
| |
| *upload_content_type = util::kContentTypeApplicationJson; |
| |
| base::Value::Dict root; |
| if (!title_.empty()) |
| root.Set("title", title_); |
| |
| if (!modified_date_.is_null()) |
| root.Set("modifiedDate", util::FormatTimeAsString(modified_date_)); |
| |
| if (!last_viewed_by_me_date_.is_null()) { |
| root.Set("lastViewedByMeDate", |
| util::FormatTimeAsString(last_viewed_by_me_date_)); |
| } |
| |
| if (!parents_.empty()) { |
| base::Value::List parents_value; |
| for (const std::string& parent_id : parents_) { |
| base::Value::Dict parent; |
| parent.Set("id", parent_id); |
| parents_value.Append(std::move(parent)); |
| } |
| root.Set("parents", base::Value(std::move(parents_value))); |
| } |
| |
| AttachProperties(properties_, root); |
| base::JSONWriter::Write(root, upload_content); |
| |
| DVLOG(1) << "FilesPatch data: " << *upload_content_type << ", [" |
| << *upload_content << "]"; |
| return true; |
| } |
| |
| //============================= FilesCopyRequest ============================== |
| |
| FilesCopyRequest::FilesCopyRequest(RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| FileResourceCallback callback) |
| : DriveApiDataRequest<FileResource>(sender, std::move(callback)), |
| url_generator_(url_generator), |
| visibility_(FILE_VISIBILITY_DEFAULT) {} |
| |
| FilesCopyRequest::~FilesCopyRequest() = default; |
| |
| HttpRequestMethod FilesCopyRequest::GetRequestType() const { |
| return HttpRequestMethod::kPost; |
| } |
| |
| GURL FilesCopyRequest::GetURLInternal() const { |
| return url_generator_.GetFilesCopyUrl( |
| file_id_, visibility_ == FILE_VISIBILITY_PRIVATE ? "PRIVATE" : ""); |
| } |
| |
| bool FilesCopyRequest::GetContentData(std::string* upload_content_type, |
| std::string* upload_content) { |
| if (parents_.empty() && title_.empty()) |
| return false; |
| |
| *upload_content_type = util::kContentTypeApplicationJson; |
| |
| base::Value::Dict root; |
| |
| if (!modified_date_.is_null()) |
| root.Set("modifiedDate", util::FormatTimeAsString(modified_date_)); |
| |
| if (!parents_.empty()) { |
| base::Value::List parents_value; |
| |
| for (const std::string& parent_id : parents_) { |
| base::Value::Dict parent; |
| parent.Set("id", parent_id); |
| parents_value.Append(std::move(parent)); |
| } |
| root.Set("parents", base::Value(std::move(parents_value))); |
| } |
| |
| if (!title_.empty()) |
| root.Set("title", title_); |
| |
| base::JSONWriter::Write(root, upload_content); |
| DVLOG(1) << "FilesCopy data: " << *upload_content_type << ", [" |
| << *upload_content << "]"; |
| return true; |
| } |
| |
| //========================= TeamDriveListRequest ============================= |
| |
| TeamDriveListRequest::TeamDriveListRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| TeamDriveListCallback callback) |
| : DriveApiDataRequest<TeamDriveList>(sender, std::move(callback)), |
| url_generator_(url_generator), |
| max_results_(30) {} |
| |
| TeamDriveListRequest::~TeamDriveListRequest() = default; |
| |
| GURL TeamDriveListRequest::GetURLInternal() const { |
| return url_generator_.GetTeamDriveListUrl(max_results_, page_token_); |
| } |
| |
| //========================= StartPageTokenRequest ============================= |
| |
| StartPageTokenRequest::StartPageTokenRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| StartPageTokenCallback callback) |
| : DriveApiDataRequest<StartPageToken>(sender, std::move(callback)), |
| url_generator_(url_generator) {} |
| |
| StartPageTokenRequest::~StartPageTokenRequest() = default; |
| |
| GURL StartPageTokenRequest::GetURLInternal() const { |
| return url_generator_.GetStartPageTokenUrl(team_drive_id_); |
| } |
| |
| //============================= FilesListRequest ============================= |
| |
| FilesListRequest::FilesListRequest(RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| FileListCallback callback) |
| : DriveApiDataRequest<FileList>(sender, std::move(callback)), |
| url_generator_(url_generator), |
| max_results_(100), |
| corpora_(FilesListCorpora::DEFAULT) {} |
| |
| FilesListRequest::~FilesListRequest() = default; |
| |
| GURL FilesListRequest::GetURLInternal() const { |
| return url_generator_.GetFilesListUrl(max_results_, page_token_, corpora_, |
| team_drive_id_, q_); |
| } |
| |
| //======================== FilesListNextPageRequest ========================= |
| |
| FilesListNextPageRequest::FilesListNextPageRequest(RequestSender* sender, |
| FileListCallback callback) |
| : DriveApiDataRequest<FileList>(sender, std::move(callback)) {} |
| |
| FilesListNextPageRequest::~FilesListNextPageRequest() = default; |
| |
| GURL FilesListNextPageRequest::GetURLInternal() const { |
| return next_link_; |
| } |
| |
| //============================ FilesDeleteRequest ============================= |
| |
| FilesDeleteRequest::FilesDeleteRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| EntryActionCallback callback) |
| : EntryActionRequest(sender, std::move(callback)), |
| url_generator_(url_generator) {} |
| |
| FilesDeleteRequest::~FilesDeleteRequest() = default; |
| |
| HttpRequestMethod FilesDeleteRequest::GetRequestType() const { |
| return HttpRequestMethod::kDelete; |
| } |
| |
| GURL FilesDeleteRequest::GetURL() const { |
| return url_generator_.GetFilesDeleteUrl(file_id_); |
| } |
| |
| std::vector<std::string> FilesDeleteRequest::GetExtraRequestHeaders() const { |
| std::vector<std::string> headers( |
| EntryActionRequest::GetExtraRequestHeaders()); |
| headers.push_back(util::GenerateIfMatchHeader(etag_)); |
| return headers; |
| } |
| |
| //============================ FilesTrashRequest ============================= |
| |
| FilesTrashRequest::FilesTrashRequest(RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| FileResourceCallback callback) |
| : DriveApiDataRequest<FileResource>(sender, std::move(callback)), |
| url_generator_(url_generator) {} |
| |
| FilesTrashRequest::~FilesTrashRequest() = default; |
| |
| HttpRequestMethod FilesTrashRequest::GetRequestType() const { |
| return HttpRequestMethod::kPost; |
| } |
| |
| GURL FilesTrashRequest::GetURLInternal() const { |
| return url_generator_.GetFilesTrashUrl(file_id_); |
| } |
| |
| //============================== AboutGetRequest ============================= |
| |
| AboutGetRequest::AboutGetRequest(RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| AboutResourceCallback callback) |
| : DriveApiDataRequest<AboutResource>(sender, std::move(callback)), |
| url_generator_(url_generator) {} |
| |
| AboutGetRequest::~AboutGetRequest() = default; |
| |
| GURL AboutGetRequest::GetURLInternal() const { |
| return url_generator_.GetAboutGetUrl(); |
| } |
| |
| //============================ ChangesListRequest =========================== |
| |
| ChangesListRequest::ChangesListRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| ChangeListCallback callback) |
| : DriveApiDataRequest<ChangeList>(sender, std::move(callback)), |
| url_generator_(url_generator), |
| include_deleted_(true), |
| max_results_(100), |
| start_change_id_(0) {} |
| |
| ChangesListRequest::~ChangesListRequest() = default; |
| |
| GURL ChangesListRequest::GetURLInternal() const { |
| return url_generator_.GetChangesListUrl(include_deleted_, max_results_, |
| page_token_, start_change_id_, |
| team_drive_id_); |
| } |
| |
| //======================== ChangesListNextPageRequest ========================= |
| |
| ChangesListNextPageRequest::ChangesListNextPageRequest( |
| RequestSender* sender, |
| ChangeListCallback callback) |
| : DriveApiDataRequest<ChangeList>(sender, std::move(callback)) {} |
| |
| ChangesListNextPageRequest::~ChangesListNextPageRequest() = default; |
| |
| GURL ChangesListNextPageRequest::GetURLInternal() const { |
| return next_link_; |
| } |
| |
| //========================== ChildrenInsertRequest ============================ |
| |
| ChildrenInsertRequest::ChildrenInsertRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| EntryActionCallback callback) |
| : EntryActionRequest(sender, std::move(callback)), |
| url_generator_(url_generator) {} |
| |
| ChildrenInsertRequest::~ChildrenInsertRequest() = default; |
| |
| HttpRequestMethod ChildrenInsertRequest::GetRequestType() const { |
| return HttpRequestMethod::kPost; |
| } |
| |
| GURL ChildrenInsertRequest::GetURL() const { |
| return url_generator_.GetChildrenInsertUrl(folder_id_); |
| } |
| |
| bool ChildrenInsertRequest::GetContentData(std::string* upload_content_type, |
| std::string* upload_content) { |
| *upload_content_type = util::kContentTypeApplicationJson; |
| |
| base::Value::Dict root; |
| root.Set("id", id_); |
| |
| base::JSONWriter::Write(root, upload_content); |
| DVLOG(1) << "InsertResource data: " << *upload_content_type << ", [" |
| << *upload_content << "]"; |
| return true; |
| } |
| |
| //========================== ChildrenDeleteRequest ============================ |
| |
| ChildrenDeleteRequest::ChildrenDeleteRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| EntryActionCallback callback) |
| : EntryActionRequest(sender, std::move(callback)), |
| url_generator_(url_generator) {} |
| |
| ChildrenDeleteRequest::~ChildrenDeleteRequest() = default; |
| |
| HttpRequestMethod ChildrenDeleteRequest::GetRequestType() const { |
| return HttpRequestMethod::kDelete; |
| } |
| |
| GURL ChildrenDeleteRequest::GetURL() const { |
| return url_generator_.GetChildrenDeleteUrl(child_id_, folder_id_); |
| } |
| |
| //======================= InitiateUploadNewFileRequest ======================= |
| |
| InitiateUploadNewFileRequest::InitiateUploadNewFileRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| const std::string& content_type, |
| int64_t content_length, |
| const std::string& parent_resource_id, |
| const std::string& title, |
| InitiateUploadCallback callback) |
| : InitiateUploadRequestBase(sender, |
| std::move(callback), |
| content_type, |
| content_length), |
| url_generator_(url_generator), |
| parent_resource_id_(parent_resource_id), |
| title_(title) {} |
| |
| InitiateUploadNewFileRequest::~InitiateUploadNewFileRequest() = default; |
| |
| GURL InitiateUploadNewFileRequest::GetURL() const { |
| return url_generator_.GetInitiateUploadNewFileUrl(!modified_date_.is_null()); |
| } |
| |
| HttpRequestMethod InitiateUploadNewFileRequest::GetRequestType() const { |
| return HttpRequestMethod::kPost; |
| } |
| |
| bool InitiateUploadNewFileRequest::GetContentData( |
| std::string* upload_content_type, |
| std::string* upload_content) { |
| *upload_content_type = util::kContentTypeApplicationJson; |
| |
| base::Value::Dict root; |
| root.Set("title", title_); |
| |
| // Fill parent link. |
| base::Value::List parents; |
| parents.Append(util::CreateParentValue(parent_resource_id_)); |
| root.Set("parents", base::Value(std::move(parents))); |
| |
| if (!modified_date_.is_null()) |
| root.Set("modifiedDate", util::FormatTimeAsString(modified_date_)); |
| |
| if (!last_viewed_by_me_date_.is_null()) { |
| root.Set("lastViewedByMeDate", |
| util::FormatTimeAsString(last_viewed_by_me_date_)); |
| } |
| |
| AttachProperties(properties_, root); |
| base::JSONWriter::Write(root, upload_content); |
| |
| DVLOG(1) << "InitiateUploadNewFile data: " << *upload_content_type << ", [" |
| << *upload_content << "]"; |
| return true; |
| } |
| |
| //===================== InitiateUploadExistingFileRequest ==================== |
| |
| InitiateUploadExistingFileRequest::InitiateUploadExistingFileRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| const std::string& content_type, |
| int64_t content_length, |
| const std::string& resource_id, |
| const std::string& etag, |
| InitiateUploadCallback callback) |
| : InitiateUploadRequestBase(sender, |
| std::move(callback), |
| content_type, |
| content_length), |
| url_generator_(url_generator), |
| resource_id_(resource_id), |
| etag_(etag) {} |
| |
| InitiateUploadExistingFileRequest::~InitiateUploadExistingFileRequest() = |
| default; |
| |
| GURL InitiateUploadExistingFileRequest::GetURL() const { |
| return url_generator_.GetInitiateUploadExistingFileUrl( |
| resource_id_, !modified_date_.is_null()); |
| } |
| |
| HttpRequestMethod InitiateUploadExistingFileRequest::GetRequestType() const { |
| return HttpRequestMethod::kPut; |
| } |
| |
| std::vector<std::string> |
| InitiateUploadExistingFileRequest::GetExtraRequestHeaders() const { |
| std::vector<std::string> headers( |
| InitiateUploadRequestBase::GetExtraRequestHeaders()); |
| headers.push_back(util::GenerateIfMatchHeader(etag_)); |
| return headers; |
| } |
| |
| bool InitiateUploadExistingFileRequest::GetContentData( |
| std::string* upload_content_type, |
| std::string* upload_content) { |
| base::Value::Dict root; |
| if (!parent_resource_id_.empty()) { |
| base::Value::List parents; |
| parents.Append(util::CreateParentValue(parent_resource_id_)); |
| root.Set("parents", base::Value(std::move(parents))); |
| } |
| |
| if (!title_.empty()) |
| root.Set("title", title_); |
| |
| if (!modified_date_.is_null()) |
| root.Set("modifiedDate", util::FormatTimeAsString(modified_date_)); |
| |
| if (!last_viewed_by_me_date_.is_null()) { |
| root.Set("lastViewedByMeDate", |
| util::FormatTimeAsString(last_viewed_by_me_date_)); |
| } |
| |
| AttachProperties(properties_, root); |
| if (root.empty()) |
| return false; |
| |
| *upload_content_type = util::kContentTypeApplicationJson; |
| base::JSONWriter::Write(root, upload_content); |
| DVLOG(1) << "InitiateUploadExistingFile data: " << *upload_content_type |
| << ", [" << *upload_content << "]"; |
| return true; |
| } |
| |
| //============================ ResumeUploadRequest =========================== |
| |
| ResumeUploadRequest::ResumeUploadRequest(RequestSender* sender, |
| const GURL& upload_location, |
| int64_t start_position, |
| int64_t end_position, |
| int64_t content_length, |
| const std::string& content_type, |
| const base::FilePath& local_file_path, |
| UploadRangeCallback callback, |
| ProgressCallback progress_callback) |
| : ResumeUploadRequestBase(sender, |
| upload_location, |
| start_position, |
| end_position, |
| content_length, |
| content_type, |
| local_file_path, |
| progress_callback), |
| callback_(std::move(callback)) { |
| DCHECK(!callback_.is_null()); |
| } |
| |
| ResumeUploadRequest::~ResumeUploadRequest() = default; |
| |
| void ResumeUploadRequest::OnRangeRequestComplete( |
| const UploadRangeResponse& response, |
| std::unique_ptr<base::Value> value) { |
| DCHECK(CalledOnValidThread()); |
| ParseFileResourceWithUploadRangeAndRun(std::move(callback_), response, |
| std::move(value)); |
| } |
| |
| //========================== GetUploadStatusRequest ========================== |
| |
| GetUploadStatusRequest::GetUploadStatusRequest(RequestSender* sender, |
| const GURL& upload_url, |
| int64_t content_length, |
| UploadRangeCallback callback) |
| : GetUploadStatusRequestBase(sender, upload_url, content_length), |
| callback_(std::move(callback)) { |
| DCHECK(!callback_.is_null()); |
| } |
| |
| GetUploadStatusRequest::~GetUploadStatusRequest() = default; |
| |
| void GetUploadStatusRequest::OnRangeRequestComplete( |
| const UploadRangeResponse& response, |
| std::unique_ptr<base::Value> value) { |
| DCHECK(CalledOnValidThread()); |
| ParseFileResourceWithUploadRangeAndRun(std::move(callback_), response, |
| std::move(value)); |
| } |
| |
| //======================= MultipartUploadNewFileDelegate ======================= |
| |
| MultipartUploadNewFileDelegate::MultipartUploadNewFileDelegate( |
| base::SequencedTaskRunner* task_runner, |
| const std::string& title, |
| const std::string& parent_resource_id, |
| const std::string& content_type, |
| int64_t content_length, |
| const base::Time& modified_date, |
| const base::Time& last_viewed_by_me_date, |
| const base::FilePath& local_file_path, |
| const Properties& properties, |
| const DriveApiUrlGenerator& url_generator, |
| FileResourceCallback callback, |
| ProgressCallback progress_callback) |
| : MultipartUploadRequestBase( |
| task_runner, |
| CreateMultipartUploadMetadataJson(title, |
| parent_resource_id, |
| modified_date, |
| last_viewed_by_me_date, |
| properties), |
| content_type, |
| content_length, |
| local_file_path, |
| std::move(callback), |
| progress_callback), |
| has_modified_date_(!modified_date.is_null()), |
| url_generator_(url_generator) {} |
| |
| MultipartUploadNewFileDelegate::~MultipartUploadNewFileDelegate() = default; |
| |
| GURL MultipartUploadNewFileDelegate::GetURL() const { |
| return url_generator_.GetMultipartUploadNewFileUrl(has_modified_date_); |
| } |
| |
| HttpRequestMethod MultipartUploadNewFileDelegate::GetRequestType() const { |
| return HttpRequestMethod::kPost; |
| } |
| |
| //====================== MultipartUploadExistingFileDelegate =================== |
| |
| MultipartUploadExistingFileDelegate::MultipartUploadExistingFileDelegate( |
| base::SequencedTaskRunner* task_runner, |
| const std::string& title, |
| const std::string& resource_id, |
| const std::string& parent_resource_id, |
| const std::string& content_type, |
| int64_t content_length, |
| const base::Time& modified_date, |
| const base::Time& last_viewed_by_me_date, |
| const base::FilePath& local_file_path, |
| const std::string& etag, |
| const Properties& properties, |
| const DriveApiUrlGenerator& url_generator, |
| FileResourceCallback callback, |
| ProgressCallback progress_callback) |
| : MultipartUploadRequestBase( |
| task_runner, |
| CreateMultipartUploadMetadataJson(title, |
| parent_resource_id, |
| modified_date, |
| last_viewed_by_me_date, |
| properties), |
| content_type, |
| content_length, |
| local_file_path, |
| std::move(callback), |
| progress_callback), |
| resource_id_(resource_id), |
| etag_(etag), |
| has_modified_date_(!modified_date.is_null()), |
| url_generator_(url_generator) {} |
| |
| MultipartUploadExistingFileDelegate::~MultipartUploadExistingFileDelegate() = |
| default; |
| |
| std::vector<std::string> |
| MultipartUploadExistingFileDelegate::GetExtraRequestHeaders() const { |
| std::vector<std::string> headers( |
| MultipartUploadRequestBase::GetExtraRequestHeaders()); |
| headers.push_back(util::GenerateIfMatchHeader(etag_)); |
| return headers; |
| } |
| |
| GURL MultipartUploadExistingFileDelegate::GetURL() const { |
| return url_generator_.GetMultipartUploadExistingFileUrl(resource_id_, |
| has_modified_date_); |
| } |
| |
| HttpRequestMethod MultipartUploadExistingFileDelegate::GetRequestType() const { |
| return HttpRequestMethod::kPut; |
| } |
| |
| //========================== DownloadFileRequest ========================== |
| |
| DownloadFileRequest::DownloadFileRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| const std::string& resource_id, |
| const base::FilePath& output_file_path, |
| DownloadActionCallback download_action_callback, |
| const GetContentCallback& get_content_callback, |
| ProgressCallback progress_callback) |
| : DownloadFileRequestBase( |
| sender, |
| std::move(download_action_callback), |
| get_content_callback, |
| progress_callback, |
| url_generator.GenerateDownloadFileUrl(resource_id), |
| output_file_path) {} |
| |
| DownloadFileRequest::~DownloadFileRequest() = default; |
| |
| //========================== PermissionsInsertRequest ========================== |
| |
| PermissionsInsertRequest::PermissionsInsertRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator, |
| EntryActionCallback callback) |
| : EntryActionRequest(sender, std::move(callback)), |
| url_generator_(url_generator), |
| type_(PERMISSION_TYPE_USER), |
| role_(PERMISSION_ROLE_READER) {} |
| |
| PermissionsInsertRequest::~PermissionsInsertRequest() = default; |
| |
| GURL PermissionsInsertRequest::GetURL() const { |
| return url_generator_.GetPermissionsInsertUrl(id_); |
| } |
| |
| HttpRequestMethod PermissionsInsertRequest::GetRequestType() const { |
| return HttpRequestMethod::kPost; |
| } |
| |
| bool PermissionsInsertRequest::GetContentData(std::string* upload_content_type, |
| std::string* upload_content) { |
| *upload_content_type = util::kContentTypeApplicationJson; |
| |
| base::Value::Dict root; |
| switch (type_) { |
| case PERMISSION_TYPE_ANYONE: |
| root.Set("type", "anyone"); |
| break; |
| case PERMISSION_TYPE_DOMAIN: |
| root.Set("type", "domain"); |
| break; |
| case PERMISSION_TYPE_GROUP: |
| root.Set("type", "group"); |
| break; |
| case PERMISSION_TYPE_USER: |
| root.Set("type", "user"); |
| break; |
| } |
| switch (role_) { |
| case PERMISSION_ROLE_OWNER: |
| root.Set("role", "owner"); |
| break; |
| case PERMISSION_ROLE_READER: |
| root.Set("role", "reader"); |
| break; |
| case PERMISSION_ROLE_WRITER: |
| root.Set("role", "writer"); |
| break; |
| case PERMISSION_ROLE_COMMENTER: |
| root.Set("role", "reader"); |
| { |
| base::Value::List list; |
| list.Append("commenter"); |
| root.Set("additionalRoles", std::move(list)); |
| } |
| break; |
| } |
| root.Set("value", value_); |
| base::JSONWriter::Write(root, upload_content); |
| return true; |
| } |
| |
| //======================= SingleBatchableDelegateRequest ======================= |
| |
| SingleBatchableDelegateRequest::SingleBatchableDelegateRequest( |
| RequestSender* sender, |
| std::unique_ptr<BatchableDelegate> delegate) |
| : DriveUrlFetchRequestBase( |
| sender, |
| base::BindRepeating( |
| &SingleBatchableDelegateRequest::OnUploadProgress, |
| // Safe to not retain as the SimpleURLLoader is owned by our base |
| // class and cannot outlive this instance. |
| base::Unretained(this)), |
| ProgressCallback()), |
| delegate_(std::move(delegate)) {} |
| |
| SingleBatchableDelegateRequest::~SingleBatchableDelegateRequest() = default; |
| |
| GURL SingleBatchableDelegateRequest::GetURL() const { |
| return delegate_->GetURL(); |
| } |
| |
| HttpRequestMethod SingleBatchableDelegateRequest::GetRequestType() const { |
| return delegate_->GetRequestType(); |
| } |
| |
| std::vector<std::string> |
| SingleBatchableDelegateRequest::GetExtraRequestHeaders() const { |
| return delegate_->GetExtraRequestHeaders(); |
| } |
| |
| void SingleBatchableDelegateRequest::Prepare(PrepareCallback callback) { |
| delegate_->Prepare(std::move(callback)); |
| } |
| |
| bool SingleBatchableDelegateRequest::GetContentData( |
| std::string* upload_content_type, |
| std::string* upload_content) { |
| return delegate_->GetContentData(upload_content_type, upload_content); |
| } |
| |
| void SingleBatchableDelegateRequest::ProcessURLFetchResults( |
| const network::mojom::URLResponseHead* response_head, |
| base::FilePath response_file, |
| std::string response_body) { |
| delegate_->NotifyResult( |
| GetErrorCode(), response_body, |
| base::BindOnce( |
| &SingleBatchableDelegateRequest::OnProcessURLFetchResultsComplete, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void SingleBatchableDelegateRequest::RunCallbackOnPrematureFailure( |
| ApiErrorCode code) { |
| delegate_->NotifyError(code); |
| } |
| |
| void SingleBatchableDelegateRequest::OnUploadProgress(int64_t current, |
| int64_t total) { |
| delegate_->NotifyUploadProgress(current, total); |
| } |
| |
| //========================== BatchUploadRequest ========================== |
| |
| BatchUploadChildEntry::BatchUploadChildEntry(BatchableDelegate* request) |
| : request(request), prepared(false), data_offset(0), data_size(0) {} |
| |
| BatchUploadChildEntry::~BatchUploadChildEntry() = default; |
| |
| BatchUploadRequest::BatchUploadRequest( |
| RequestSender* sender, |
| const DriveApiUrlGenerator& url_generator) |
| : DriveUrlFetchRequestBase( |
| sender, |
| // Safe to not retain as the SimpleURLLoader is owned by our base |
| // class and cannot outlive this instance. |
| base::BindRepeating(&BatchUploadRequest::OnUploadProgress, |
| base::Unretained(this)), |
| ProgressCallback()), |
| sender_(sender), |
| url_generator_(url_generator), |
| committed_(false), |
| last_progress_value_(0) {} |
| |
| BatchUploadRequest::~BatchUploadRequest() = default; |
| |
| void BatchUploadRequest::SetBoundaryForTesting(const std::string& boundary) { |
| boundary_ = boundary; |
| } |
| |
| void BatchUploadRequest::AddRequest(BatchableDelegate* request) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(request); |
| DCHECK(GetChildEntry(request) == child_requests_.end()); |
| DCHECK(!committed_); |
| child_requests_.push_back(std::make_unique<BatchUploadChildEntry>(request)); |
| request->Prepare(base::BindOnce(&BatchUploadRequest::OnChildRequestPrepared, |
| weak_ptr_factory_.GetWeakPtr(), request)); |
| } |
| |
| void BatchUploadRequest::OnChildRequestPrepared(RequestID request_id, |
| ApiErrorCode result) { |
| DCHECK(CalledOnValidThread()); |
| auto const child = GetChildEntry(request_id); |
| DCHECK(child != child_requests_.end()); |
| if (IsSuccessfulDriveApiErrorCode(result)) { |
| (*child)->prepared = true; |
| } else { |
| (*child)->request->NotifyError(result); |
| child_requests_.erase(child); |
| } |
| MayCompletePrepare(); |
| } |
| |
| void BatchUploadRequest::Commit() { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!committed_); |
| if (child_requests_.empty()) { |
| Cancel(); |
| } else { |
| committed_ = true; |
| MayCompletePrepare(); |
| } |
| } |
| |
| void BatchUploadRequest::Prepare(PrepareCallback callback) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(callback); |
| prepare_callback_ = std::move(callback); |
| MayCompletePrepare(); |
| } |
| |
| void BatchUploadRequest::Cancel() { |
| child_requests_.clear(); |
| UrlFetchRequestBase::Cancel(); |
| } |
| |
| // Obtains corresponding child entry of |request_id|. Returns NULL if the |
| // entry is not found. |
| std::vector<std::unique_ptr<BatchUploadChildEntry>>::iterator |
| BatchUploadRequest::GetChildEntry(RequestID request_id) { |
| for (auto it = child_requests_.begin(); it != child_requests_.end(); ++it) { |
| if ((*it)->request.get() == request_id) |
| return it; |
| } |
| return child_requests_.end(); |
| } |
| |
| void BatchUploadRequest::MayCompletePrepare() { |
| if (!committed_ || prepare_callback_.is_null()) |
| return; |
| for (const auto& child : child_requests_) { |
| if (!child->prepared) |
| return; |
| } |
| |
| // Build multipart body here. |
| std::vector<ContentTypeAndData> parts; |
| for (const auto& child : child_requests_) { |
| std::string type; |
| std::string data; |
| const bool result = child->request->GetContentData(&type, &data); |
| // Upload request must have content data. |
| DCHECK(result); |
| |
| const GURL url = child->request->GetURL(); |
| HttpRequestMethod method = child->request->GetRequestType(); |
| const std::string header = base::StringPrintf( |
| kBatchUploadRequestFormat, HttpRequestMethodToString(method).c_str(), |
| url.path().c_str(), url_generator_.GetBatchUploadUrl().host().c_str(), |
| type.c_str()); |
| |
| child->data_offset = header.size(); |
| child->data_size = data.size(); |
| |
| parts.push_back(ContentTypeAndData({kHttpContentType, header + data})); |
| } |
| |
| std::vector<uint64_t> part_data_offset; |
| GenerateMultipartBody(MultipartType::kMixed, boundary_, parts, |
| &upload_content_, &part_data_offset); |
| DCHECK(part_data_offset.size() == child_requests_.size()); |
| for (size_t i = 0; i < child_requests_.size(); ++i) { |
| child_requests_[i]->data_offset += part_data_offset[i]; |
| } |
| std::move(prepare_callback_).Run(HTTP_SUCCESS); |
| } |
| |
| bool BatchUploadRequest::GetContentData(std::string* upload_content_type, |
| std::string* upload_content_data) { |
| upload_content_type->assign(upload_content_.type); |
| upload_content_data->assign(upload_content_.data); |
| return true; |
| } |
| |
| base::WeakPtr<BatchUploadRequest> |
| BatchUploadRequest::GetWeakPtrAsBatchUploadRequest() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| GURL BatchUploadRequest::GetURL() const { |
| return url_generator_.GetBatchUploadUrl(); |
| } |
| |
| HttpRequestMethod BatchUploadRequest::GetRequestType() const { |
| return HttpRequestMethod::kPut; |
| } |
| |
| std::vector<std::string> BatchUploadRequest::GetExtraRequestHeaders() const { |
| std::vector<std::string> headers; |
| headers.push_back(kBatchUploadHeader); |
| return headers; |
| } |
| |
| void BatchUploadRequest::ProcessURLFetchResults( |
| const network::mojom::URLResponseHead* response_head, |
| base::FilePath response_file, |
| std::string response_body) { |
| if (!IsSuccessfulDriveApiErrorCode(GetErrorCode())) { |
| RunCallbackOnPrematureFailure(GetErrorCode()); |
| sender_->RequestFinished(this); |
| return; |
| } |
| |
| std::string content_type; |
| if (response_head) { |
| response_head->headers->EnumerateHeader( |
| /* need only first header */ nullptr, "Content-Type", &content_type); |
| } |
| |
| std::vector<MultipartHttpResponse> parts; |
| if (!ParseMultipartResponse(content_type, response_body, &parts) || |
| child_requests_.size() != parts.size()) { |
| RunCallbackOnPrematureFailure(PARSE_ERROR); |
| sender_->RequestFinished(this); |
| return; |
| } |
| |
| for (size_t i = 0; i < parts.size(); ++i) { |
| BatchableDelegate* delegate = child_requests_[i]->request.get(); |
| // Pass ownership of |delegate| so that child_requests_.clear() won't |
| // kill the delegate. It has to be deleted after the notification. |
| delegate->NotifyResult( |
| parts[i].code, parts[i].body, |
| base::BindOnce(&base::DeletePointer<BatchableDelegate>, |
| child_requests_[i]->request.release())); |
| } |
| child_requests_.clear(); |
| |
| sender_->RequestFinished(this); |
| } |
| |
| void BatchUploadRequest::RunCallbackOnPrematureFailure(ApiErrorCode code) { |
| for (const auto& child : child_requests_) |
| child->request->NotifyError(code); |
| child_requests_.clear(); |
| } |
| |
| void BatchUploadRequest::OnUploadProgress(int64_t current, int64_t total) { |
| for (const auto& child : child_requests_) { |
| if (child->data_offset <= current && |
| current <= child->data_offset + child->data_size) { |
| child->request->NotifyUploadProgress(current - child->data_offset, |
| child->data_size); |
| } else if (last_progress_value_ < child->data_offset + child->data_size && |
| child->data_offset + child->data_size < current) { |
| child->request->NotifyUploadProgress(child->data_size, child->data_size); |
| } |
| } |
| last_progress_value_ = current; |
| } |
| } // namespace google_apis::drive |