// Copyright 2019 Google LLC
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google LLC nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "tools/windows/converter_exe/winhttp_client.h"

#include <assert.h>
#include <stdlib.h>
#include <windows.h>
#include <winhttp.h>
#include <vector>

namespace crash {

namespace internal {

// This class implements HttpClient based on WinInet APIs.
class WinHttpClient : public HttpClient {
 public:
  virtual ~WinHttpClient() {}
  virtual bool CrackUrl(const TCHAR* url,
                        DWORD flags,
                        TCHAR* scheme,
                        size_t scheme_buffer_length,
                        TCHAR* host,
                        size_t host_buffer_length,
                        TCHAR* uri,
                        size_t uri_buffer_length,
                        int* port) const;
  virtual bool Open(const TCHAR* user_agent,
                    DWORD access_type,
                    const TCHAR* proxy_name,
                    const TCHAR* proxy_bypass,
                    HttpHandle* session_handle) const;
  virtual bool Connect(HttpHandle session_handle,
                       const TCHAR* server,
                       int port,
                       HttpHandle* connection_handle) const;
  virtual bool OpenRequest(HttpHandle connection_handle,
                           const TCHAR* verb,
                           const TCHAR* uri,
                           const TCHAR* version,
                           const TCHAR* referrer,
                           bool is_secure,
                           HttpHandle* request_handle) const;
  virtual bool SendRequest(HttpHandle request_handle,
                           const TCHAR* headers,
                           DWORD headers_length) const;
  virtual bool ReceiveResponse(HttpHandle request_handle) const;
  virtual bool GetHttpStatusCode(HttpHandle request_handle,
                                 int* status_code) const;
  virtual bool GetContentLength(HttpHandle request_handle,
                                DWORD* content_length) const;
  virtual bool ReadData(HttpHandle request_handle,
                        void* buffer,
                        DWORD buffer_length,
                        DWORD* bytes_read) const;
  virtual bool Close(HttpHandle handle) const;

 private:
  static DWORD MapAccessType(DWORD access_type);
  static HINTERNET ToHINTERNET(HttpHandle handle);
  static HttpHandle FromHINTERNET(HINTERNET handle);
};

bool WinHttpClient::CrackUrl(const TCHAR* url,
                             DWORD flags,
                             TCHAR* scheme,
                             size_t scheme_buffer_length,
                             TCHAR* host,
                             size_t host_buffer_length,
                             TCHAR* uri,
                             size_t uri_buffer_length,
                             int* port) const {
  assert(url);
  assert(scheme);
  assert(host);
  assert(uri);
  assert(port);

  URL_COMPONENTS url_comp = {0};
  url_comp.dwStructSize = sizeof(url_comp);
  url_comp.lpszScheme = scheme;
  url_comp.dwSchemeLength = static_cast<DWORD>(scheme_buffer_length);
  url_comp.lpszHostName = host;
  url_comp.dwHostNameLength = static_cast<DWORD>(host_buffer_length);
  url_comp.lpszUrlPath = uri;
  url_comp.dwUrlPathLength = static_cast<DWORD>(uri_buffer_length);

  bool result = !!::WinHttpCrackUrl(url, 0, flags, &url_comp);
  if (result) {
    *port = static_cast<int>(url_comp.nPort);
  }
  return result;
}

bool WinHttpClient::Open(const TCHAR* user_agent,
                         DWORD access_type,
                         const TCHAR* proxy_name,
                         const TCHAR* proxy_bypass,
                         HttpHandle* session_handle)  const {
  *session_handle = FromHINTERNET(::WinHttpOpen(user_agent,
                                                MapAccessType(access_type),
                                                proxy_name,
                                                proxy_bypass,
                                                0));

  return !!(*session_handle);
}

bool WinHttpClient::Connect(HttpHandle session_handle,
                            const TCHAR* server,
                            int port,
                            HttpHandle* connection_handle) const {
  assert(server);

  // Uses NULL user name and password to connect.
  *connection_handle = FromHINTERNET(::WinHttpConnect(
                                         ToHINTERNET(session_handle),
                                         server,
                                         static_cast<INTERNET_PORT>(port),
                                         NULL));
  return !!(*connection_handle);
}

bool WinHttpClient::OpenRequest(HttpHandle connection_handle,
                                const TCHAR* verb,
                                const TCHAR* uri,
                                const TCHAR* version,
                                const TCHAR* referrer,
                                bool is_secure,
                                HttpHandle* request_handle) const {
  assert(connection_handle);
  assert(verb);
  assert(uri);
  assert(request_handle);

  *request_handle = FromHINTERNET(::WinHttpOpenRequest(
                                      ToHINTERNET(connection_handle),
                                      verb,
                                      uri,
                                      version,
                                      referrer,
                                      WINHTTP_DEFAULT_ACCEPT_TYPES,
                                      is_secure ? WINHTTP_FLAG_SECURE : 0));
  return !!(*request_handle);
}

bool WinHttpClient::SendRequest(HttpHandle request_handle,
                                const TCHAR* headers,
                                DWORD headers_length) const {
  assert(request_handle);

  return !!::WinHttpSendRequest(ToHINTERNET(request_handle),
                                headers,
                                headers_length,
                                NULL,
                                0,
                                WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH,
                                NULL);
}

bool WinHttpClient::ReceiveResponse(HttpHandle request_handle) const {
  assert(request_handle);

  return !!::WinHttpReceiveResponse(ToHINTERNET(request_handle), NULL);
}

bool WinHttpClient::GetHttpStatusCode(HttpHandle request_handle,
                                      int* status_code) const {
  TCHAR http_status_string[4] = {0};
  DWORD http_status_string_size = sizeof(http_status_string);
  if (!::WinHttpQueryHeaders(ToHINTERNET(request_handle),
                             WINHTTP_QUERY_STATUS_CODE,
                             WINHTTP_HEADER_NAME_BY_INDEX,
                             static_cast<void*>(&http_status_string),
                             &http_status_string_size, 0)) {
    return false;
  }

  *status_code = static_cast<DWORD>(_tcstol(http_status_string, NULL, 10));
  return true;
}

bool WinHttpClient::GetContentLength(HttpHandle request_handle,
                                     DWORD* content_length) const {
  assert(request_handle);
  assert(content_length);

  TCHAR content_length_string[11] = {0};
  DWORD content_length_string_size = sizeof(content_length_string);
  if (!::WinHttpQueryHeaders(ToHINTERNET(request_handle),
                             WINHTTP_QUERY_CONTENT_LENGTH,
                             WINHTTP_HEADER_NAME_BY_INDEX,
                             static_cast<void*>(&content_length_string),
                             &content_length_string_size, 0)) {
    *content_length = kUnknownContentLength;
  } else {
    *content_length =
        static_cast<DWORD>(wcstol(content_length_string, NULL, 10));
  }
  return true;
}

bool WinHttpClient::ReadData(HttpHandle request_handle,
                             void* buffer,
                             DWORD buffer_length,
                             DWORD* bytes_read) const {
  assert(request_handle);
  assert(buffer);
  assert(bytes_read);

  DWORD bytes_read_local = 0;
  if (!::WinHttpReadData(ToHINTERNET(request_handle),
                         buffer,
                         buffer_length,
                         &bytes_read_local)) {
    return false;
  }
  *bytes_read = bytes_read_local;
  return true;
}

bool WinHttpClient::Close(HttpHandle handle) const {
  assert(handle);
  return !!::WinHttpCloseHandle(ToHINTERNET(handle));
}

DWORD WinHttpClient::MapAccessType(DWORD access_type) {
  switch (static_cast<AccessType>(access_type)) {
    case ACCESS_TYPE_PRECONFIG:
    default:
      return WINHTTP_ACCESS_TYPE_DEFAULT_PROXY;
    case ACCESS_TYPE_DIRECT:
      return WINHTTP_ACCESS_TYPE_NO_PROXY;
    case ACCESS_TYPE_PROXY:
      return WINHTTP_ACCESS_TYPE_NAMED_PROXY;
  }
}


HINTERNET WinHttpClient::ToHINTERNET(HttpHandle handle) {
  return static_cast<HINTERNET>(handle);
}

HttpHandle WinHttpClient::FromHINTERNET(HINTERNET handle) {
  return static_cast<HttpHandle>(handle);
}

}  // namespace internal

HttpClient* CreateWinHttpClient(const TCHAR* url) {
  assert(url);

  internal::WinHttpClient winhttp;
  wchar_t scheme[16] = {0};
  wchar_t host[256] = {0};
  wchar_t path[256] = {0};
  int port = 0;

  if (!winhttp.CrackUrl(url,
                        0,
                        scheme,
                        sizeof(scheme)/sizeof(scheme[0]),
                        host,
                        sizeof(host)/sizeof(host[0]),
                        path,
                        sizeof(path)/sizeof(path[0]),
                        &port)) {
    return NULL;
  }

  if (_wcsicmp(scheme, L"https") == 0) {
    // Winhttp under WINE doesn't support wildcard certificates, so avoid 
    // to use it if the scheme is https. The caller should fall back to
    // use wininet if NULL is returned.
    return NULL;
  }

  return new internal::WinHttpClient();
}

}  // namespace crash
