// Copyright (c) 2006-2008 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 "base/clipboard_util.h"

#include <shellapi.h>
#include <shlwapi.h>
#include <wininet.h>

#include "base/basictypes.h"
#include "base/logging.h"
#include "base/scoped_handle.h"
#include "base/string_util.h"

namespace {

bool GetUrlFromHDrop(IDataObject* data_object, std::wstring* url,
                     std::wstring* title) {
  DCHECK(data_object && url && title);

  STGMEDIUM medium;
  if (FAILED(data_object->GetData(ClipboardUtil::GetCFHDropFormat(), &medium)))
    return false;

  HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));

  if (!hdrop)
    return false;

  bool success = false;
  wchar_t filename[MAX_PATH];
  if (DragQueryFileW(hdrop, 0, filename, arraysize(filename))) {
    wchar_t url_buffer[INTERNET_MAX_URL_LENGTH];
    if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") &&
        GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, url_buffer,
                                 arraysize(url_buffer), filename)) {
      *url = url_buffer;
      PathRemoveExtension(filename);
      title->assign(PathFindFileName(filename));
      success = true;
    }
  }

  DragFinish(hdrop);
  GlobalUnlock(medium.hGlobal);
  // We don't need to call ReleaseStgMedium here because as far as I can tell,
  // DragFinish frees the hGlobal for us.
  return success;
}

bool SplitUrlAndTitle(const std::wstring& str, std::wstring* url,
    std::wstring* title) {
  DCHECK(url && title);
  size_t newline_pos = str.find('\n');
  bool success = false;
  if (newline_pos != std::string::npos) {
    *url = str.substr(0, newline_pos);
    title->assign(str.substr(newline_pos + 1));
    success = true;
  } else {
    *url = str;
    title->assign(str);
    success = true;
  }
  return success;
}

}  // namespace


FORMATETC* ClipboardUtil::GetUrlFormat() {
  static UINT cf = RegisterClipboardFormat(CFSTR_INETURLA);
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetUrlWFormat() {
  static UINT cf = RegisterClipboardFormat(CFSTR_INETURLW);
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetMozUrlFormat() {
  // The format is "URL\nTitle"
  static UINT cf = RegisterClipboardFormat(L"text/x-moz-url");
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetPlainTextFormat() {
  // We don't need to register this format since it's a built in format.
  static FORMATETC format = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetPlainTextWFormat() {
  // We don't need to register this format since it's a built in format.
  static FORMATETC format = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1,
                             TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetFilenameWFormat() {
  static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEW);
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetFilenameFormat()
{
  static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEA);
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetHtmlFormat() {
  static UINT cf = RegisterClipboardFormat(L"HTML Format");
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetTextHtmlFormat() {
  static UINT cf = RegisterClipboardFormat(L"text/html");
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetCFHDropFormat() {
  // We don't need to register this format since it's a built in format.
  static FORMATETC format = {CF_HDROP, 0, DVASPECT_CONTENT, -1,
                             TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetFileDescriptorFormat() {
  static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetFileContentFormatZero() {
  static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS);
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
  return &format;
}

FORMATETC* ClipboardUtil::GetWebKitSmartPasteFormat() {
  static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format");
  static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL};
  return &format;
}


bool ClipboardUtil::HasUrl(IDataObject* data_object) {
  DCHECK(data_object);
  return SUCCEEDED(data_object->QueryGetData(GetMozUrlFormat())) ||
      SUCCEEDED(data_object->QueryGetData(GetUrlWFormat())) ||
      SUCCEEDED(data_object->QueryGetData(GetUrlFormat())) ||
      SUCCEEDED(data_object->QueryGetData(GetFilenameWFormat())) ||
      SUCCEEDED(data_object->QueryGetData(GetFilenameFormat()));
}

bool ClipboardUtil::HasFilenames(IDataObject* data_object) {
  DCHECK(data_object);
  return SUCCEEDED(data_object->QueryGetData(GetCFHDropFormat()));
}

bool ClipboardUtil::HasPlainText(IDataObject* data_object) {
  DCHECK(data_object);
  return SUCCEEDED(data_object->QueryGetData(GetPlainTextWFormat())) ||
      SUCCEEDED(data_object->QueryGetData(GetPlainTextFormat()));
}


bool ClipboardUtil::GetUrl(IDataObject* data_object,
    std::wstring* url, std::wstring* title) {
  DCHECK(data_object && url && title);
  if (!HasUrl(data_object))
    return false;

  // Try to extract a URL from |data_object| in a variety of formats.
  STGMEDIUM store;
  if (GetUrlFromHDrop(data_object, url, title)) {
    return true;
  }

  if (SUCCEEDED(data_object->GetData(GetMozUrlFormat(), &store)) ||
      SUCCEEDED(data_object->GetData(GetUrlWFormat(), &store))) {
    // Mozilla URL format or unicode URL
    ScopedHGlobal<wchar_t> data(store.hGlobal);
    bool success = SplitUrlAndTitle(data.get(), url, title);
    ReleaseStgMedium(&store);
    if (success)
      return true;
  }

  if (SUCCEEDED(data_object->GetData(GetUrlFormat(), &store))) {
    // URL using ascii
    ScopedHGlobal<char> data(store.hGlobal);
    bool success = SplitUrlAndTitle(UTF8ToWide(data.get()), url, title);
    ReleaseStgMedium(&store);
    if (success)
      return true;
  }

  if (SUCCEEDED(data_object->GetData(GetFilenameWFormat(), &store))) {
    // filename using unicode
    ScopedHGlobal<wchar_t> data(store.hGlobal);
    bool success = false;
    if (data.get() && data.get()[0] && (PathFileExists(data.get()) ||
                                        PathIsUNC(data.get()))) {
      wchar_t file_url[INTERNET_MAX_URL_LENGTH];
      DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]);
      if (SUCCEEDED(::UrlCreateFromPathW(data.get(), file_url, &file_url_len,
                                         0))) {
        *url = file_url;
        title->assign(file_url);
        success = true;
      }
    }
    ReleaseStgMedium(&store);
    if (success)
      return true;
  }

  if (SUCCEEDED(data_object->GetData(GetFilenameFormat(), &store))) {
    // filename using ascii
    ScopedHGlobal<char> data(store.hGlobal);
    bool success = false;
    if (data.get() && data.get()[0] && (PathFileExistsA(data.get()) ||
                                        PathIsUNCA(data.get()))) {
      char file_url[INTERNET_MAX_URL_LENGTH];
      DWORD file_url_len = sizeof(file_url) / sizeof(file_url[0]);
      if (SUCCEEDED(::UrlCreateFromPathA(data.get(), file_url, &file_url_len, 0))) {
        *url = UTF8ToWide(file_url);
        title->assign(*url);
        success = true;
      }
    }
    ReleaseStgMedium(&store);
    if (success)
      return true;
  }

  return false;
}

bool ClipboardUtil::GetFilenames(IDataObject* data_object,
                                 std::vector<std::wstring>* filenames) {
  DCHECK(data_object && filenames);
  if (!HasFilenames(data_object))
    return false;

  STGMEDIUM medium;
  if (FAILED(data_object->GetData(GetCFHDropFormat(), &medium)))
    return false;

  HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal));
  if (!hdrop)
    return false;

  const int kMaxFilenameLen = 4096;
  const unsigned num_files = DragQueryFileW(hdrop, 0xffffffff, 0, 0);
  for (unsigned int i = 0; i < num_files; ++i) {
    wchar_t filename[kMaxFilenameLen];
    if (!DragQueryFileW(hdrop, i, filename, kMaxFilenameLen))
      continue;
    filenames->push_back(filename);
  }

  DragFinish(hdrop);
  GlobalUnlock(medium.hGlobal);
  // We don't need to call ReleaseStgMedium here because as far as I can tell,
  // DragFinish frees the hGlobal for us.
  return true;
}

bool ClipboardUtil::GetPlainText(IDataObject* data_object,
                                 std::wstring* plain_text) {
  DCHECK(data_object && plain_text);
  if (!HasPlainText(data_object))
    return false;

  STGMEDIUM store;
  bool success = false;
  if (SUCCEEDED(data_object->GetData(GetPlainTextWFormat(), &store))) {
    // Unicode text
    ScopedHGlobal<wchar_t> data(store.hGlobal);
    plain_text->assign(data.get());
    ReleaseStgMedium(&store);
    success = true;
  } else if (SUCCEEDED(data_object->GetData(GetPlainTextFormat(), &store))) {
    // ascii text
    ScopedHGlobal<char> data(store.hGlobal);
    plain_text->assign(UTF8ToWide(data.get()));
    ReleaseStgMedium(&store);
    success = true;
  } else {
    //If a file is dropped on the window, it does not provide either of the
    //plain text formats, so here we try to forcibly get a url.
    std::wstring title;
    success = GetUrl(data_object, plain_text, &title);
  }

  return success;
}

bool ClipboardUtil::GetCFHtml(IDataObject* data_object,
                              std::wstring* cf_html) {
  DCHECK(data_object && cf_html);
  if (FAILED(data_object->QueryGetData(GetHtmlFormat())))
    return false;

  STGMEDIUM store;
  if (FAILED(data_object->GetData(GetHtmlFormat(), &store)))
    return false;

  // MS CF html
  ScopedHGlobal<char> data(store.hGlobal);
  cf_html->assign(UTF8ToWide(std::string(data.get(), data.Size())));
  ReleaseStgMedium(&store);
  return true;
}

bool ClipboardUtil::GetTextHtml(IDataObject* data_object,
                                std::wstring* text_html) {
  DCHECK(data_object && text_html);
  if (FAILED(data_object->QueryGetData(GetTextHtmlFormat())))
    return false;

  STGMEDIUM store;
  if (FAILED(data_object->GetData(GetTextHtmlFormat(), &store)))
    return false;

  // raw html
  ScopedHGlobal<wchar_t> data(store.hGlobal);
  text_html->assign(data.get());
  ReleaseStgMedium(&store);
  return true;
}

bool ClipboardUtil::GetFileContents(IDataObject* data_object,
    std::wstring* filename, std::string* file_contents) {
  DCHECK(data_object && filename && file_contents);
  bool has_data =
      SUCCEEDED(data_object->QueryGetData(GetFileContentFormatZero())) ||
      SUCCEEDED(data_object->QueryGetData(GetFileDescriptorFormat()));

  if (!has_data)
    return false;

  STGMEDIUM content;
  // The call to GetData can be very slow depending on what is in
  // |data_object|.
  if (SUCCEEDED(data_object->GetData(GetFileContentFormatZero(), &content))) {
    if (TYMED_HGLOBAL == content.tymed) {
      ScopedHGlobal<char> data(content.hGlobal);
      // The size includes the trailing NULL byte.  We don't want it.
      file_contents->assign(data.get(), data.Size() - 1);
    }
    ReleaseStgMedium(&content);
  }

  STGMEDIUM description;
  if (SUCCEEDED(data_object->GetData(GetFileDescriptorFormat(),
                                     &description))) {
    ScopedHGlobal<FILEGROUPDESCRIPTOR> fgd(description.hGlobal);
    // We expect there to be at least one file in here.
    DCHECK(fgd->cItems >= 1);
    filename->assign(fgd->fgd[0].cFileName);
    ReleaseStgMedium(&description);
  }
  return true;
}