// Copyright 2014 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 "content/browser/tracing/trace_uploader.h"

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/memory/shared_memory.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/mime_util.h"
#include "net/base/network_delegate.h"
#include "net/proxy/proxy_config.h"
#include "net/proxy/proxy_config_service.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_builder.h"
#include "net/url_request/url_request_context_getter.h"
#include "third_party/zlib/zlib.h"
#include "url/gurl.h"

namespace content {
namespace {
const char kUploadContentType[] = "multipart/form-data";
const char kMultipartBoundary[] =
    "----**--yradnuoBgoLtrapitluMklaTelgooG--**----";

const int kHttpResponseOk = 200;

}  // namespace

TraceUploader::TraceUploader(const std::string& product,
                             const std::string& version,
                             const std::string& upload_url,
                             net::URLRequestContextGetter* request_context)
    : product_(product),
      version_(version),
      upload_url_(upload_url),
      request_context_(request_context) {
  DCHECK(!product_.empty());
  DCHECK(!version_.empty());
  DCHECK(!upload_url_.empty());
}

TraceUploader::~TraceUploader() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
}

void TraceUploader::OnURLFetchComplete(const net::URLFetcher* source) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK_EQ(source, url_fetcher_.get());
  int response_code = source->GetResponseCode();
  string report_id;
  string error_message;
  bool success = (response_code == kHttpResponseOk);
  if (success) {
    source->GetResponseAsString(&report_id);
  } else {
    error_message = "Uploading failed, response code: " +
                    base::IntToString(response_code);
  }

  BrowserThread::PostTask(
      content::BrowserThread::UI,
      FROM_HERE,
      base::Bind(done_callback_, success, report_id, error_message));
  url_fetcher_.reset();
}

void TraceUploader::OnURLFetchUploadProgress(
    const net::URLFetcher* source, int64 current, int64 total) {
  DCHECK(url_fetcher_.get());

  LOG(WARNING) << "Upload progress: " << current << " of " << total;
  BrowserThread::PostTask(
      content::BrowserThread::UI,
      FROM_HERE,
      base::Bind(progress_callback_, current, total));
}

void TraceUploader::DoUpload(
    const std::string& file_contents,
    UploadProgressCallback progress_callback,
    UploadDoneCallback done_callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::FILE);
  DCHECK(!url_fetcher_.get());

  progress_callback_ = progress_callback;
  done_callback_ = done_callback;

  if (url_fetcher_.get()) {
    OnUploadError("Already uploading.");
  }

  scoped_ptr<char[]> compressed_contents(new char[kMaxUploadBytes]);
  int compressed_bytes;
  if (!Compress(file_contents, kMaxUploadBytes, compressed_contents.get(),
                &compressed_bytes)) {
    OnUploadError("Compressing file failed.");
    return;
  }

  std::string post_data;
  SetupMultipart("trace.json.gz",
                 std::string(compressed_contents.get(), compressed_bytes),
                 &post_data);

  content::BrowserThread::PostTask(
      content::BrowserThread::UI, FROM_HERE,
      base::Bind(&TraceUploader::CreateAndStartURLFetcher,
                 base::Unretained(this),
                 post_data));
}

void TraceUploader::OnUploadError(std::string error_message) {
  LOG(ERROR) << error_message;
  content::BrowserThread::PostTask(
      content::BrowserThread::UI,
      FROM_HERE,
      base::Bind(done_callback_, false, "", error_message));
}

void TraceUploader::SetupMultipart(const std::string& trace_filename,
                                   const std::string& trace_contents,
                                   std::string* post_data) {
  net::AddMultipartValueForUpload("prod", product_, kMultipartBoundary, "",
                                  post_data);
  net::AddMultipartValueForUpload("ver", version_ + "-trace",
                                  kMultipartBoundary, "", post_data);
  net::AddMultipartValueForUpload("guid", "0", kMultipartBoundary,
                                  "", post_data);
  net::AddMultipartValueForUpload("type", "trace", kMultipartBoundary,
                                  "", post_data);
  // No minidump means no need for crash to process the report.
  net::AddMultipartValueForUpload("should_process", "false", kMultipartBoundary,
                                  "", post_data);

  AddTraceFile(trace_filename, trace_contents, post_data);

  net::AddMultipartFinalDelimiterForUpload(kMultipartBoundary, post_data);
}

void TraceUploader::AddTraceFile(const std::string& trace_filename,
                                 const std::string& trace_contents,
                                 std::string* post_data) {
  post_data->append("--");
  post_data->append(kMultipartBoundary);
  post_data->append("\r\n");
  post_data->append("Content-Disposition: form-data; name=\"trace\"");
  post_data->append("; filename=\"");
  post_data->append(trace_filename);
  post_data->append("\"\r\n");
  post_data->append("Content-Type: application/gzip\r\n\r\n");
  post_data->append(trace_contents);
  post_data->append("\r\n");
}

bool TraceUploader::Compress(std::string input,
                             int max_compressed_bytes,
                             char* compressed,
                             int* compressed_bytes) {
  DCHECK(compressed);
  DCHECK(compressed_bytes);
  z_stream stream = {0};
  int result = deflateInit2(&stream,
                            Z_DEFAULT_COMPRESSION,
                            Z_DEFLATED,
                            // 16 is added to produce a gzip header + trailer.
                            MAX_WBITS + 16,
                            8,  // memLevel = 8 is default.
                            Z_DEFAULT_STRATEGY);
  DCHECK_EQ(Z_OK, result);
  stream.next_in = reinterpret_cast<uint8*>(&input[0]);
  stream.avail_in = input.size();
  stream.next_out = reinterpret_cast<uint8*>(compressed);
  stream.avail_out = max_compressed_bytes;
  // Do a one-shot compression. This will return Z_STREAM_END only if |output|
  // is large enough to hold all compressed data.
  result = deflate(&stream, Z_FINISH);
  bool success = (result == Z_STREAM_END);
  result = deflateEnd(&stream);
  DCHECK(result == Z_OK || result == Z_DATA_ERROR);

  if (success)
    *compressed_bytes = max_compressed_bytes - stream.avail_out;

  LOG(WARNING) << "input size: " << input.size()
               << ", output size: " << *compressed_bytes;
  return success;
}

void TraceUploader::CreateAndStartURLFetcher(const std::string& post_data) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(!url_fetcher_.get());

  std::string content_type = kUploadContentType;
  content_type.append("; boundary=");
  content_type.append(kMultipartBoundary);

  url_fetcher_.reset(
      net::URLFetcher::Create(GURL(upload_url_), net::URLFetcher::POST, this));
  url_fetcher_->SetRequestContext(request_context_);
  url_fetcher_->SetUploadData(content_type, post_data);
  url_fetcher_->Start();
}

}  // namespace content