blob: f534efa021ee125f1de7d653ee56e06e106e5f81 [file] [log] [blame]
// 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/drive_api_requests.h"
#include <stddef.h>
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_writer.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.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/request_util.h"
#include "google_apis/drive/time_util.h"
#include "net/base/url_util.h"
#include "net/http/http_response_headers.h"
namespace google_apis {
namespace 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=";
// UMA names.
const char kUMADriveBatchUploadResponseCode[] = "Drive.BatchUploadResponseCode";
const char kUMADriveTotalFileCountInBatchUpload[] =
"Drive.TotalFileCountInBatchUpload";
const char kUMADriveTotalFileSizeInBatchUpload[] =
"Drive.TotalFileSizeInBatchUpload";
// 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(
const 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) {
callback.Run(UploadRangeResponse(DRIVE_PARSE_ERROR,
response.start_position_received,
response.end_position_received),
std::unique_ptr<FileResource>());
return;
}
}
callback.Run(response, std::move(file_resource));
}
// Attaches |properties| to the |request_body| if |properties| is not empty.
// |request_body| must not be NULL.
void AttachProperties(const Properties& properties,
base::DictionaryValue* request_body) {
DCHECK(request_body);
if (properties.empty())
return;
base::ListValue* const properties_value = new base::ListValue;
for (const auto& property : properties) {
std::unique_ptr<base::DictionaryValue> property_value(
new base::DictionaryValue);
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->SetString("visibility", visibility_as_string);
property_value->SetString("key", property.key());
property_value->SetString("value", property.value());
properties_value->Append(std::move(property_value));
}
request_body->Set("properties", 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::DictionaryValue root;
if (!title.empty())
root.SetString("title", title);
// Fill parent link.
if (!parent_resource_id.empty()) {
std::unique_ptr<base::ListValue> parents(new base::ListValue);
parents->Append(google_apis::util::CreateParentValue(parent_resource_id));
root.Set("parents", parents.release());
}
if (!modified_date.is_null()) {
root.SetString("modifiedDate",
google_apis::util::FormatTimeAsString(modified_date));
}
if (!last_viewed_by_me_date.is_null()) {
root.SetString("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() : code(HTTP_SUCCESS) {
}
MultipartHttpResponse::~MultipartHttpResponse() {
}
// 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;
base::StringPiece content_type_piece(content_type);
if (!content_type_piece.starts_with(kMultipartMixedMimeTypePrefix)) {
return false;
}
content_type_piece.remove_prefix(
base::StringPiece(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.CopyToString(&boundary);
const std::string header = "--" + boundary;
const std::string terminator = "--" + boundary + "--";
std::vector<base::StringPiece> 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;
DriveApiErrorCode code = DRIVE_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 (line.starts_with(kHttpStatusPrefix)) {
int int_code;
base::StringToInt(
line.substr(base::StringPiece(kHttpStatusPrefix).size()),
&int_code);
if (int_code > 0)
code = static_cast<DriveApiErrorCode>(int_code);
else
code = DRIVE_PARSE_ERROR;
} else {
code = DRIVE_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 base::StringPiece 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.push_back(MultipartHttpResponse());
responses.back().code = DRIVE_PARSE_ERROR;
break;
case STATE_PART_HTTP_HEADER:
responses.push_back(MultipartHttpResponse());
responses.back().code = code;
break;
case STATE_PART_HTTP_BODY:
// Drop the last kHttpBr.
if (!body.empty())
body.resize(body.size() - 2);
responses.push_back(MultipartHttpResponse());
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) {
line.AppendToString(&body);
body.append(kHttpBr);
}
}
parts->swap(responses);
return true;
}
Property::Property() : visibility_(VISIBILITY_PRIVATE) {
}
Property::~Property() {
}
//============================ DriveApiPartialFieldRequest ====================
DriveApiPartialFieldRequest::DriveApiPartialFieldRequest(
RequestSender* sender) : UrlFetchRequestBase(sender) {
}
DriveApiPartialFieldRequest::~DriveApiPartialFieldRequest() {
}
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,
bool use_internal_endpoint,
const FileResourceCallback& callback)
: DriveApiDataRequest<FileResource>(sender, callback),
url_generator_(url_generator),
use_internal_endpoint_(use_internal_endpoint) {
DCHECK(!callback.is_null());
}
FilesGetRequest::~FilesGetRequest() {}
GURL FilesGetRequest::GetURLInternal() const {
return url_generator_.GetFilesGetUrl(file_id_,
use_internal_endpoint_,
embed_origin_);
}
//============================ FilesAuthorizeRequest ===========================
FilesAuthorizeRequest::FilesAuthorizeRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const FileResourceCallback& callback)
: DriveApiDataRequest<FileResource>(sender, callback),
url_generator_(url_generator) {
DCHECK(!callback.is_null());
}
FilesAuthorizeRequest::~FilesAuthorizeRequest() {}
net::URLFetcher::RequestType FilesAuthorizeRequest::GetRequestType() const {
return net::URLFetcher::POST;
}
GURL FilesAuthorizeRequest::GetURLInternal() const {
return url_generator_.GetFilesAuthorizeUrl(file_id_, app_id_);
}
//============================ FilesInsertRequest ============================
FilesInsertRequest::FilesInsertRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const FileResourceCallback& callback)
: DriveApiDataRequest<FileResource>(sender, callback),
url_generator_(url_generator),
visibility_(FILE_VISIBILITY_DEFAULT) {
DCHECK(!callback.is_null());
}
FilesInsertRequest::~FilesInsertRequest() {}
net::URLFetcher::RequestType FilesInsertRequest::GetRequestType() const {
return net::URLFetcher::POST;
}
bool FilesInsertRequest::GetContentData(std::string* upload_content_type,
std::string* upload_content) {
*upload_content_type = util::kContentTypeApplicationJson;
base::DictionaryValue root;
if (!last_viewed_by_me_date_.is_null()) {
root.SetString("lastViewedByMeDate",
util::FormatTimeAsString(last_viewed_by_me_date_));
}
if (!mime_type_.empty())
root.SetString("mimeType", mime_type_);
if (!modified_date_.is_null())
root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
if (!parents_.empty()) {
base::ListValue* parents_value = new base::ListValue;
for (size_t i = 0; i < parents_.size(); ++i) {
std::unique_ptr<base::DictionaryValue> parent(new base::DictionaryValue);
parent->SetString("id", parents_[i]);
parents_value->Append(std::move(parent));
}
root.Set("parents", parents_value);
}
if (!title_.empty())
root.SetString("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,
const FileResourceCallback& callback)
: DriveApiDataRequest<FileResource>(sender, callback),
url_generator_(url_generator),
set_modified_date_(false),
update_viewed_date_(true) {
DCHECK(!callback.is_null());
}
FilesPatchRequest::~FilesPatchRequest() {}
net::URLFetcher::RequestType FilesPatchRequest::GetRequestType() const {
return net::URLFetcher::PATCH;
}
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::DictionaryValue root;
if (!title_.empty())
root.SetString("title", title_);
if (!modified_date_.is_null())
root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
if (!last_viewed_by_me_date_.is_null()) {
root.SetString("lastViewedByMeDate",
util::FormatTimeAsString(last_viewed_by_me_date_));
}
if (!parents_.empty()) {
base::ListValue* parents_value = new base::ListValue;
for (size_t i = 0; i < parents_.size(); ++i) {
std::unique_ptr<base::DictionaryValue> parent(new base::DictionaryValue);
parent->SetString("id", parents_[i]);
parents_value->Append(std::move(parent));
}
root.Set("parents", 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,
const FileResourceCallback& callback)
: DriveApiDataRequest<FileResource>(sender, callback),
url_generator_(url_generator),
visibility_(FILE_VISIBILITY_DEFAULT) {
DCHECK(!callback.is_null());
}
FilesCopyRequest::~FilesCopyRequest() {
}
net::URLFetcher::RequestType FilesCopyRequest::GetRequestType() const {
return net::URLFetcher::POST;
}
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::DictionaryValue root;
if (!modified_date_.is_null())
root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
if (!parents_.empty()) {
base::ListValue* parents_value = new base::ListValue;
for (size_t i = 0; i < parents_.size(); ++i) {
std::unique_ptr<base::DictionaryValue> parent(new base::DictionaryValue);
parent->SetString("id", parents_[i]);
parents_value->Append(std::move(parent));
}
root.Set("parents", parents_value);
}
if (!title_.empty())
root.SetString("title", title_);
base::JSONWriter::Write(root, upload_content);
DVLOG(1) << "FilesCopy data: " << *upload_content_type << ", ["
<< *upload_content << "]";
return true;
}
//============================= FilesListRequest =============================
FilesListRequest::FilesListRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const FileListCallback& callback)
: DriveApiDataRequest<FileList>(sender, callback),
url_generator_(url_generator),
max_results_(100) {
DCHECK(!callback.is_null());
}
FilesListRequest::~FilesListRequest() {}
GURL FilesListRequest::GetURLInternal() const {
return url_generator_.GetFilesListUrl(max_results_, page_token_, q_);
}
//======================== FilesListNextPageRequest =========================
FilesListNextPageRequest::FilesListNextPageRequest(
RequestSender* sender,
const FileListCallback& callback)
: DriveApiDataRequest<FileList>(sender, callback) {
DCHECK(!callback.is_null());
}
FilesListNextPageRequest::~FilesListNextPageRequest() {
}
GURL FilesListNextPageRequest::GetURLInternal() const {
return next_link_;
}
//============================ FilesDeleteRequest =============================
FilesDeleteRequest::FilesDeleteRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const EntryActionCallback& callback)
: EntryActionRequest(sender, callback),
url_generator_(url_generator) {
DCHECK(!callback.is_null());
}
FilesDeleteRequest::~FilesDeleteRequest() {}
net::URLFetcher::RequestType FilesDeleteRequest::GetRequestType() const {
return net::URLFetcher::DELETE_REQUEST;
}
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,
const FileResourceCallback& callback)
: DriveApiDataRequest<FileResource>(sender, callback),
url_generator_(url_generator) {
DCHECK(!callback.is_null());
}
FilesTrashRequest::~FilesTrashRequest() {}
net::URLFetcher::RequestType FilesTrashRequest::GetRequestType() const {
return net::URLFetcher::POST;
}
GURL FilesTrashRequest::GetURLInternal() const {
return url_generator_.GetFilesTrashUrl(file_id_);
}
//============================== AboutGetRequest =============================
AboutGetRequest::AboutGetRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const AboutResourceCallback& callback)
: DriveApiDataRequest<AboutResource>(sender, callback),
url_generator_(url_generator) {
DCHECK(!callback.is_null());
}
AboutGetRequest::~AboutGetRequest() {}
GURL AboutGetRequest::GetURLInternal() const {
return url_generator_.GetAboutGetUrl();
}
//============================ ChangesListRequest ===========================
ChangesListRequest::ChangesListRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const ChangeListCallback& callback)
: DriveApiDataRequest<ChangeList>(sender, callback),
url_generator_(url_generator),
include_deleted_(true),
max_results_(100),
start_change_id_(0) {
DCHECK(!callback.is_null());
}
ChangesListRequest::~ChangesListRequest() {}
GURL ChangesListRequest::GetURLInternal() const {
return url_generator_.GetChangesListUrl(
include_deleted_, max_results_, page_token_, start_change_id_);
}
//======================== ChangesListNextPageRequest =========================
ChangesListNextPageRequest::ChangesListNextPageRequest(
RequestSender* sender,
const ChangeListCallback& callback)
: DriveApiDataRequest<ChangeList>(sender, callback) {
DCHECK(!callback.is_null());
}
ChangesListNextPageRequest::~ChangesListNextPageRequest() {
}
GURL ChangesListNextPageRequest::GetURLInternal() const {
return next_link_;
}
//============================== AppsListRequest ===========================
AppsListRequest::AppsListRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
bool use_internal_endpoint,
const AppListCallback& callback)
: DriveApiDataRequest<AppList>(sender, callback),
url_generator_(url_generator),
use_internal_endpoint_(use_internal_endpoint) {
DCHECK(!callback.is_null());
}
AppsListRequest::~AppsListRequest() {}
GURL AppsListRequest::GetURLInternal() const {
return url_generator_.GetAppsListUrl(use_internal_endpoint_);
}
//============================== AppsDeleteRequest ===========================
AppsDeleteRequest::AppsDeleteRequest(RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const EntryActionCallback& callback)
: EntryActionRequest(sender, callback),
url_generator_(url_generator) {
DCHECK(!callback.is_null());
}
AppsDeleteRequest::~AppsDeleteRequest() {}
net::URLFetcher::RequestType AppsDeleteRequest::GetRequestType() const {
return net::URLFetcher::DELETE_REQUEST;
}
GURL AppsDeleteRequest::GetURL() const {
return url_generator_.GetAppsDeleteUrl(app_id_);
}
//========================== ChildrenInsertRequest ============================
ChildrenInsertRequest::ChildrenInsertRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const EntryActionCallback& callback)
: EntryActionRequest(sender, callback),
url_generator_(url_generator) {
DCHECK(!callback.is_null());
}
ChildrenInsertRequest::~ChildrenInsertRequest() {}
net::URLFetcher::RequestType ChildrenInsertRequest::GetRequestType() const {
return net::URLFetcher::POST;
}
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::DictionaryValue root;
root.SetString("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,
const EntryActionCallback& callback)
: EntryActionRequest(sender, callback),
url_generator_(url_generator) {
DCHECK(!callback.is_null());
}
ChildrenDeleteRequest::~ChildrenDeleteRequest() {}
net::URLFetcher::RequestType ChildrenDeleteRequest::GetRequestType() const {
return net::URLFetcher::DELETE_REQUEST;
}
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,
const InitiateUploadCallback& callback)
: InitiateUploadRequestBase(sender, callback, content_type, content_length),
url_generator_(url_generator),
parent_resource_id_(parent_resource_id),
title_(title) {}
InitiateUploadNewFileRequest::~InitiateUploadNewFileRequest() {}
GURL InitiateUploadNewFileRequest::GetURL() const {
return url_generator_.GetInitiateUploadNewFileUrl(!modified_date_.is_null());
}
net::URLFetcher::RequestType
InitiateUploadNewFileRequest::GetRequestType() const {
return net::URLFetcher::POST;
}
bool InitiateUploadNewFileRequest::GetContentData(
std::string* upload_content_type,
std::string* upload_content) {
*upload_content_type = util::kContentTypeApplicationJson;
base::DictionaryValue root;
root.SetString("title", title_);
// Fill parent link.
std::unique_ptr<base::ListValue> parents(new base::ListValue);
parents->Append(util::CreateParentValue(parent_resource_id_));
root.Set("parents", parents.release());
if (!modified_date_.is_null())
root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
if (!last_viewed_by_me_date_.is_null()) {
root.SetString("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,
const InitiateUploadCallback& callback)
: InitiateUploadRequestBase(sender, callback, content_type, content_length),
url_generator_(url_generator),
resource_id_(resource_id),
etag_(etag) {}
InitiateUploadExistingFileRequest::~InitiateUploadExistingFileRequest() {}
GURL InitiateUploadExistingFileRequest::GetURL() const {
return url_generator_.GetInitiateUploadExistingFileUrl(
resource_id_, !modified_date_.is_null());
}
net::URLFetcher::RequestType
InitiateUploadExistingFileRequest::GetRequestType() const {
return net::URLFetcher::PUT;
}
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::DictionaryValue root;
if (!parent_resource_id_.empty()) {
std::unique_ptr<base::ListValue> parents(new base::ListValue);
parents->Append(util::CreateParentValue(parent_resource_id_));
root.Set("parents", parents.release());
}
if (!title_.empty())
root.SetString("title", title_);
if (!modified_date_.is_null())
root.SetString("modifiedDate", util::FormatTimeAsString(modified_date_));
if (!last_viewed_by_me_date_.is_null()) {
root.SetString("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,
const UploadRangeCallback& callback,
const ProgressCallback& progress_callback)
: ResumeUploadRequestBase(sender,
upload_location,
start_position,
end_position,
content_length,
content_type,
local_file_path),
callback_(callback),
progress_callback_(progress_callback) {
DCHECK(!callback_.is_null());
}
ResumeUploadRequest::~ResumeUploadRequest() {}
void ResumeUploadRequest::OnRangeRequestComplete(
const UploadRangeResponse& response,
std::unique_ptr<base::Value> value) {
DCHECK(CalledOnValidThread());
ParseFileResourceWithUploadRangeAndRun(callback_, response, std::move(value));
}
void ResumeUploadRequest::OnURLFetchUploadProgress(
const net::URLFetcher* source,
int64_t current,
int64_t total) {
if (!progress_callback_.is_null())
progress_callback_.Run(current, total);
}
//========================== GetUploadStatusRequest ==========================
GetUploadStatusRequest::GetUploadStatusRequest(
RequestSender* sender,
const GURL& upload_url,
int64_t content_length,
const UploadRangeCallback& callback)
: GetUploadStatusRequestBase(sender, upload_url, content_length),
callback_(callback) {
DCHECK(!callback.is_null());
}
GetUploadStatusRequest::~GetUploadStatusRequest() {}
void GetUploadStatusRequest::OnRangeRequestComplete(
const UploadRangeResponse& response,
std::unique_ptr<base::Value> value) {
DCHECK(CalledOnValidThread());
ParseFileResourceWithUploadRangeAndRun(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,
const FileResourceCallback& callback,
const 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,
callback,
progress_callback),
has_modified_date_(!modified_date.is_null()),
url_generator_(url_generator) {}
MultipartUploadNewFileDelegate::~MultipartUploadNewFileDelegate() {
}
GURL MultipartUploadNewFileDelegate::GetURL() const {
return url_generator_.GetMultipartUploadNewFileUrl(has_modified_date_);
}
net::URLFetcher::RequestType MultipartUploadNewFileDelegate::GetRequestType()
const {
return net::URLFetcher::POST;
}
//====================== 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,
const FileResourceCallback& callback,
const 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,
callback,
progress_callback),
resource_id_(resource_id),
etag_(etag),
has_modified_date_(!modified_date.is_null()),
url_generator_(url_generator) {}
MultipartUploadExistingFileDelegate::~MultipartUploadExistingFileDelegate() {
}
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_);
}
net::URLFetcher::RequestType
MultipartUploadExistingFileDelegate::GetRequestType() const {
return net::URLFetcher::PUT;
}
//========================== DownloadFileRequest ==========================
DownloadFileRequest::DownloadFileRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const std::string& resource_id,
const base::FilePath& output_file_path,
const DownloadActionCallback& download_action_callback,
const GetContentCallback& get_content_callback,
const ProgressCallback& progress_callback)
: DownloadFileRequestBase(
sender,
download_action_callback,
get_content_callback,
progress_callback,
url_generator.GenerateDownloadFileUrl(resource_id),
output_file_path) {
}
DownloadFileRequest::~DownloadFileRequest() {
}
//========================== PermissionsInsertRequest ==========================
PermissionsInsertRequest::PermissionsInsertRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator,
const EntryActionCallback& callback)
: EntryActionRequest(sender, callback),
url_generator_(url_generator),
type_(PERMISSION_TYPE_USER),
role_(PERMISSION_ROLE_READER) {
}
PermissionsInsertRequest::~PermissionsInsertRequest() {
}
GURL PermissionsInsertRequest::GetURL() const {
return url_generator_.GetPermissionsInsertUrl(id_);
}
net::URLFetcher::RequestType
PermissionsInsertRequest::GetRequestType() const {
return net::URLFetcher::POST;
}
bool PermissionsInsertRequest::GetContentData(std::string* upload_content_type,
std::string* upload_content) {
*upload_content_type = util::kContentTypeApplicationJson;
base::DictionaryValue root;
switch (type_) {
case PERMISSION_TYPE_ANYONE:
root.SetString("type", "anyone");
break;
case PERMISSION_TYPE_DOMAIN:
root.SetString("type", "domain");
break;
case PERMISSION_TYPE_GROUP:
root.SetString("type", "group");
break;
case PERMISSION_TYPE_USER:
root.SetString("type", "user");
break;
}
switch (role_) {
case PERMISSION_ROLE_OWNER:
root.SetString("role", "owner");
break;
case PERMISSION_ROLE_READER:
root.SetString("role", "reader");
break;
case PERMISSION_ROLE_WRITER:
root.SetString("role", "writer");
break;
case PERMISSION_ROLE_COMMENTER:
root.SetString("role", "reader");
{
base::ListValue* list = new base::ListValue;
list->AppendString("commenter");
root.Set("additionalRoles", list);
}
break;
}
root.SetString("value", value_);
base::JSONWriter::Write(root, upload_content);
return true;
}
//======================= SingleBatchableDelegateRequest =======================
SingleBatchableDelegateRequest::SingleBatchableDelegateRequest(
RequestSender* sender,
std::unique_ptr<BatchableDelegate> delegate)
: UrlFetchRequestBase(sender),
delegate_(std::move(delegate)),
weak_ptr_factory_(this) {}
SingleBatchableDelegateRequest::~SingleBatchableDelegateRequest() {
}
GURL SingleBatchableDelegateRequest::GetURL() const {
return delegate_->GetURL();
}
net::URLFetcher::RequestType SingleBatchableDelegateRequest::GetRequestType()
const {
return delegate_->GetRequestType();
}
std::vector<std::string>
SingleBatchableDelegateRequest::GetExtraRequestHeaders() const {
return delegate_->GetExtraRequestHeaders();
}
void SingleBatchableDelegateRequest::Prepare(const PrepareCallback& callback) {
delegate_->Prepare(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 net::URLFetcher* source) {
delegate_->NotifyResult(
GetErrorCode(), response_writer()->data(),
base::Bind(
&SingleBatchableDelegateRequest::OnProcessURLFetchResultsComplete,
weak_ptr_factory_.GetWeakPtr()));
}
void SingleBatchableDelegateRequest::RunCallbackOnPrematureFailure(
DriveApiErrorCode code) {
delegate_->NotifyError(code);
}
void SingleBatchableDelegateRequest::OnURLFetchUploadProgress(
const net::URLFetcher* source,
int64_t current,
int64_t total) {
delegate_->NotifyUploadProgress(source, current, total);
}
//========================== BatchUploadRequest ==========================
BatchUploadChildEntry::BatchUploadChildEntry(BatchableDelegate* request)
: request(request), prepared(false), data_offset(0), data_size(0) {
}
BatchUploadChildEntry::~BatchUploadChildEntry() {
}
BatchUploadRequest::BatchUploadRequest(
RequestSender* sender,
const DriveApiUrlGenerator& url_generator)
: UrlFetchRequestBase(sender),
sender_(sender),
url_generator_(url_generator),
committed_(false),
last_progress_value_(0),
weak_ptr_factory_(this) {
}
BatchUploadRequest::~BatchUploadRequest() {
}
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(base::MakeUnique<BatchUploadChildEntry>(request));
request->Prepare(base::Bind(&BatchUploadRequest::OnChildRequestPrepared,
weak_ptr_factory_.GetWeakPtr(), request));
}
void BatchUploadRequest::OnChildRequestPrepared(RequestID request_id,
DriveApiErrorCode 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(const PrepareCallback& callback) {
DCHECK(CalledOnValidThread());
DCHECK(!callback.is_null());
prepare_callback_ = 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.
int64_t total_size = 0;
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();
std::string method;
switch (child->request->GetRequestType()) {
case net::URLFetcher::POST:
method = "POST";
break;
case net::URLFetcher::PUT:
method = "PUT";
break;
default:
NOTREACHED();
break;
}
const std::string header = base::StringPrintf(
kBatchUploadRequestFormat, 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();
total_size += data.size();
parts.push_back(ContentTypeAndData({kHttpContentType, header + data}));
}
UMA_HISTOGRAM_COUNTS_100(kUMADriveTotalFileCountInBatchUpload, parts.size());
UMA_HISTOGRAM_MEMORY_KB(kUMADriveTotalFileSizeInBatchUpload,
total_size / 1024);
std::vector<uint64_t> part_data_offset;
GenerateMultipartBody(MULTIPART_MIXED, 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];
}
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();
}
net::URLFetcher::RequestType BatchUploadRequest::GetRequestType() const {
return net::URLFetcher::PUT;
}
std::vector<std::string> BatchUploadRequest::GetExtraRequestHeaders() const {
std::vector<std::string> headers;
headers.push_back(kBatchUploadHeader);
return headers;
}
void BatchUploadRequest::ProcessURLFetchResults(const net::URLFetcher* source) {
// Return the detailed raw HTTP code if the error code is abstracted
// DRIVE_OTHER_ERROR. If HTTP connection is failed and the status code is -1,
// return network status error.
int histogram_error = 0;
if (GetErrorCode() != DRIVE_OTHER_ERROR) {
histogram_error = GetErrorCode();
} else if (source->GetResponseCode() != -1) {
histogram_error = source->GetResponseCode();
} else {
histogram_error = source->GetStatus().error();
}
UMA_HISTOGRAM_SPARSE_SLOWLY(
kUMADriveBatchUploadResponseCode, histogram_error);
if (!IsSuccessfulDriveApiErrorCode(GetErrorCode())) {
RunCallbackOnPrematureFailure(GetErrorCode());
sender_->RequestFinished(this);
return;
}
std::string content_type;
source->GetResponseHeaders()->EnumerateHeader(
/* need only first header */ NULL, "Content-Type", &content_type);
std::vector<MultipartHttpResponse> parts;
if (!ParseMultipartResponse(content_type, response_writer()->data(),
&parts) ||
child_requests_.size() != parts.size()) {
RunCallbackOnPrematureFailure(DRIVE_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::Bind(&base::DeletePointer<BatchableDelegate>,
child_requests_[i]->request.release()));
}
child_requests_.clear();
sender_->RequestFinished(this);
}
void BatchUploadRequest::RunCallbackOnPrematureFailure(DriveApiErrorCode code) {
for (const auto& child : child_requests_)
child->request->NotifyError(code);
child_requests_.clear();
}
void BatchUploadRequest::OnURLFetchUploadProgress(const net::URLFetcher* source,
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(source, 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(source, child->data_size,
child->data_size);
}
}
last_progress_value_ = current;
}
} // namespace drive
} // namespace google_apis