// 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/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(new 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.
ScopedVector<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 (auto* child : child_requests_) {
    if (!child->prepared)
      return;
  }

  // Build multipart body here.
  int64_t total_size = 0;
  std::vector<ContentTypeAndData> parts;
  for (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 (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 (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
