// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/plugin_download_helper.h"

#if defined(OS_WIN)
#include <windows.h>

#include "base/file_util.h"
#include "chrome/browser/net/url_request_tracking.h"
#include "net/base/io_buffer.h"

PluginDownloadUrlHelper::PluginDownloadUrlHelper(
    const std::string& download_url,
    int source_child_unique_id,
    gfx::NativeWindow caller_window,
    PluginDownloadUrlHelper::DownloadDelegate* delegate)
    : download_file_request_(NULL),
      download_file_buffer_(new net::IOBuffer(kDownloadFileBufferSize)),
      download_file_caller_window_(caller_window),
      download_url_(download_url),
      download_source_child_unique_id_(source_child_unique_id),
      delegate_(delegate) {
  memset(download_file_buffer_->data(), 0, kDownloadFileBufferSize);
  download_file_.reset(new net::FileStream());
}

PluginDownloadUrlHelper::~PluginDownloadUrlHelper() {
  if (download_file_request_) {
    delete download_file_request_;
    download_file_request_ = NULL;
  }
}

void PluginDownloadUrlHelper::InitiateDownload(
    net::URLRequestContext* request_context) {
  download_file_request_ = new net::URLRequest(GURL(download_url_), this);
  chrome_browser_net::SetOriginPIDForRequest(
      download_source_child_unique_id_, download_file_request_);
  download_file_request_->set_context(request_context);
  download_file_request_->Start();
}

void PluginDownloadUrlHelper::OnAuthRequired(
    net::URLRequest* request,
    net::AuthChallengeInfo* auth_info) {
  net::URLRequest::Delegate::OnAuthRequired(request, auth_info);
  DownloadCompletedHelper(false);
}

void PluginDownloadUrlHelper::OnSSLCertificateError(
    net::URLRequest* request,
    int cert_error,
    net::X509Certificate* cert) {
  net::URLRequest::Delegate::OnSSLCertificateError(request, cert_error, cert);
  DownloadCompletedHelper(false);
}

void PluginDownloadUrlHelper::OnResponseStarted(net::URLRequest* request) {
  if (!download_file_->IsOpen()) {
    // This is safe because once the temp file has been safely created, an
    // attacker can't drop a symlink etc into place.
    file_util::CreateTemporaryFile(&download_file_path_);
    download_file_->Open(download_file_path_,
                         base::PLATFORM_FILE_CREATE_ALWAYS |
                         base::PLATFORM_FILE_READ | base::PLATFORM_FILE_WRITE);
    if (!download_file_->IsOpen()) {
      NOTREACHED();
      OnDownloadCompleted(request);
      return;
    }
  }
  if (!request->status().is_success()) {
    OnDownloadCompleted(request);
  } else {
    // Initiate a read.
    int bytes_read = 0;
    if (!request->Read(download_file_buffer_, kDownloadFileBufferSize,
                       &bytes_read)) {
      // If the error is not an IO pending, then we're done
      // reading.
      if (!request->status().is_io_pending()) {
        OnDownloadCompleted(request);
      }
    } else if (bytes_read == 0) {
      OnDownloadCompleted(request);
    } else {
      OnReadCompleted(request, bytes_read);
    }
  }
}

void PluginDownloadUrlHelper::OnReadCompleted(net::URLRequest* request,
                                              int bytes_read) {
  DCHECK(download_file_->IsOpen());

  if (bytes_read == 0) {
    OnDownloadCompleted(request);
    return;
  }

  int request_bytes_read = bytes_read;

  while (request->status().is_success()) {
    int bytes_written = download_file_->Write(download_file_buffer_->data(),
        request_bytes_read, NULL);
    DCHECK((bytes_written < 0) || (bytes_written == request_bytes_read));

    if ((bytes_written < 0) || (bytes_written != request_bytes_read)) {
      DownloadCompletedHelper(false);
      break;
    }

    // Start reading
    request_bytes_read = 0;
    if (!request->Read(download_file_buffer_, kDownloadFileBufferSize,
                       &request_bytes_read)) {
      if (!request->status().is_io_pending()) {
        // If the error is not an IO pending, then we're done
        // reading.
        OnDownloadCompleted(request);
      }
      break;
    } else if (request_bytes_read == 0) {
      OnDownloadCompleted(request);
      break;
    }
  }
}

void PluginDownloadUrlHelper::OnDownloadCompleted(net::URLRequest* request) {
  bool success = true;
  if (!request->status().is_success()) {
    success = false;
  } else if (!download_file_->IsOpen()) {
    success = false;
  }

  DownloadCompletedHelper(success);
}

void PluginDownloadUrlHelper::DownloadCompletedHelper(bool success) {
  if (download_file_->IsOpen()) {
      download_file_.reset();
  }

  if (success) {
    FilePath new_download_file_path =
      download_file_path_.DirName().AppendASCII(
          download_file_request_->url().ExtractFileName());

    file_util::Delete(new_download_file_path, false);

    if (!file_util::ReplaceFileW(download_file_path_,
                                 new_download_file_path)) {
      DLOG(ERROR) << "Failed to rename file:"
                  << download_file_path_.value()
                  << " to file:"
                  << new_download_file_path.value();
    } else {
      download_file_path_ = new_download_file_path;
    }
  }

  if (delegate_) {
    delegate_->OnDownloadCompleted(download_file_path_, success);
  } else {
    std::wstring path = download_file_path_.value();
    COPYDATASTRUCT download_file_data = {0};
    download_file_data.cbData =
        static_cast<unsigned long>((path.length() + 1) * sizeof(wchar_t));
    download_file_data.lpData = const_cast<wchar_t *>(path.c_str());
    download_file_data.dwData = success;

    if (::IsWindow(download_file_caller_window_)) {
      ::SendMessage(download_file_caller_window_, WM_COPYDATA, NULL,
                    reinterpret_cast<LPARAM>(&download_file_data));
    }
  }

  // Don't access any members after this.
  delete this;
}

#endif  // OS_WIN