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

#ifndef LIBBRILLO_BRILLO_HTTP_HTTP_REQUEST_H_
#define LIBBRILLO_BRILLO_HTTP_HTTP_REQUEST_H_

#include <limits>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <base/macros.h>
#include <brillo/brillo_export.h>
#include <brillo/errors/error.h>
#include <brillo/http/http_connection.h>
#include <brillo/http/http_transport.h>

namespace brillo {
namespace http {

// HTTP request verbs
namespace request_type {
BRILLO_EXPORT extern const char kOptions[];
BRILLO_EXPORT extern const char kGet[];
BRILLO_EXPORT extern const char kHead[];
BRILLO_EXPORT extern const char kPost[];
BRILLO_EXPORT extern const char kPut[];
BRILLO_EXPORT extern const char kPatch[];  // Non-standard HTTP/1.1 verb
BRILLO_EXPORT extern const char kDelete[];
BRILLO_EXPORT extern const char kTrace[];
BRILLO_EXPORT extern const char kConnect[];
BRILLO_EXPORT extern const char kCopy[];   // Non-standard HTTP/1.1 verb
BRILLO_EXPORT extern const char kMove[];   // Non-standard HTTP/1.1 verb
}  // namespace request_type

// HTTP request header names
namespace request_header {
BRILLO_EXPORT extern const char kAccept[];
BRILLO_EXPORT extern const char kAcceptCharset[];
BRILLO_EXPORT extern const char kAcceptEncoding[];
BRILLO_EXPORT extern const char kAcceptLanguage[];
BRILLO_EXPORT extern const char kAllow[];
BRILLO_EXPORT extern const char kAuthorization[];
BRILLO_EXPORT extern const char kCacheControl[];
BRILLO_EXPORT extern const char kConnection[];
BRILLO_EXPORT extern const char kContentEncoding[];
BRILLO_EXPORT extern const char kContentLanguage[];
BRILLO_EXPORT extern const char kContentLength[];
BRILLO_EXPORT extern const char kContentLocation[];
BRILLO_EXPORT extern const char kContentMd5[];
BRILLO_EXPORT extern const char kContentRange[];
BRILLO_EXPORT extern const char kContentType[];
BRILLO_EXPORT extern const char kCookie[];
BRILLO_EXPORT extern const char kDate[];
BRILLO_EXPORT extern const char kExpect[];
BRILLO_EXPORT extern const char kExpires[];
BRILLO_EXPORT extern const char kFrom[];
BRILLO_EXPORT extern const char kHost[];
BRILLO_EXPORT extern const char kIfMatch[];
BRILLO_EXPORT extern const char kIfModifiedSince[];
BRILLO_EXPORT extern const char kIfNoneMatch[];
BRILLO_EXPORT extern const char kIfRange[];
BRILLO_EXPORT extern const char kIfUnmodifiedSince[];
BRILLO_EXPORT extern const char kLastModified[];
BRILLO_EXPORT extern const char kMaxForwards[];
BRILLO_EXPORT extern const char kPragma[];
BRILLO_EXPORT extern const char kProxyAuthorization[];
BRILLO_EXPORT extern const char kRange[];
BRILLO_EXPORT extern const char kReferer[];
BRILLO_EXPORT extern const char kTE[];
BRILLO_EXPORT extern const char kTrailer[];
BRILLO_EXPORT extern const char kTransferEncoding[];
BRILLO_EXPORT extern const char kUpgrade[];
BRILLO_EXPORT extern const char kUserAgent[];
BRILLO_EXPORT extern const char kVia[];
BRILLO_EXPORT extern const char kWarning[];
}  // namespace request_header

// HTTP response header names
namespace response_header {
BRILLO_EXPORT extern const char kAcceptRanges[];
BRILLO_EXPORT extern const char kAge[];
BRILLO_EXPORT extern const char kAllow[];
BRILLO_EXPORT extern const char kCacheControl[];
BRILLO_EXPORT extern const char kConnection[];
BRILLO_EXPORT extern const char kContentEncoding[];
BRILLO_EXPORT extern const char kContentLanguage[];
BRILLO_EXPORT extern const char kContentLength[];
BRILLO_EXPORT extern const char kContentLocation[];
BRILLO_EXPORT extern const char kContentMd5[];
BRILLO_EXPORT extern const char kContentRange[];
BRILLO_EXPORT extern const char kContentType[];
BRILLO_EXPORT extern const char kDate[];
BRILLO_EXPORT extern const char kETag[];
BRILLO_EXPORT extern const char kExpires[];
BRILLO_EXPORT extern const char kLastModified[];
BRILLO_EXPORT extern const char kLocation[];
BRILLO_EXPORT extern const char kPragma[];
BRILLO_EXPORT extern const char kProxyAuthenticate[];
BRILLO_EXPORT extern const char kRetryAfter[];
BRILLO_EXPORT extern const char kServer[];
BRILLO_EXPORT extern const char kSetCookie[];
BRILLO_EXPORT extern const char kTrailer[];
BRILLO_EXPORT extern const char kTransferEncoding[];
BRILLO_EXPORT extern const char kUpgrade[];
BRILLO_EXPORT extern const char kVary[];
BRILLO_EXPORT extern const char kVia[];
BRILLO_EXPORT extern const char kWarning[];
BRILLO_EXPORT extern const char kWwwAuthenticate[];
}  // namespace response_header

// HTTP request status (error) codes
namespace status_code {
// OK to continue with request
static const int Continue = 100;
// Server has switched protocols in upgrade header
static const int SwitchProtocols = 101;

// Request completed
static const int Ok = 200;
// Object created, reason = new URI
static const int Created = 201;
// Async completion (TBS)
static const int Accepted = 202;
// Partial completion
static const int Partial = 203;
// No info to return
static const int NoContent = 204;
// Request completed, but clear form
static const int ResetContent = 205;
// Partial GET fulfilled
static const int PartialContent = 206;

// Server couldn't decide what to return
static const int Ambiguous = 300;
// Object permanently moved
static const int Moved = 301;
// Object temporarily moved
static const int Redirect = 302;
// Redirection w/ new access method
static const int RedirectMethod = 303;
// If-Modified-Since was not modified
static const int NotModified = 304;
// Redirection to proxy, location header specifies proxy to use
static const int UseProxy = 305;
// HTTP/1.1: keep same verb
static const int RedirectKeepVerb = 307;

// Invalid syntax
static const int BadRequest = 400;
// Access denied
static const int Denied = 401;
// Payment required
static const int PaymentRequired = 402;
// Request forbidden
static const int Forbidden = 403;
// Object not found
static const int NotFound = 404;
// Method is not allowed
static const int BadMethod = 405;
// No response acceptable to client found
static const int NoneAcceptable = 406;
// Proxy authentication required
static const int ProxyAuthRequired = 407;
// Server timed out waiting for request
static const int RequestTimeout = 408;
// User should resubmit with more info
static const int Conflict = 409;
// The resource is no longer available
static const int Gone = 410;
// The server refused to accept request w/o a length
static const int LengthRequired = 411;
// Precondition given in request failed
static const int PrecondionFailed = 412;
// Request entity was too large
static const int RequestTooLarge = 413;
// Request URI too long
static const int UriTooLong = 414;
// Unsupported media type
static const int UnsupportedMedia = 415;
// Retry after doing the appropriate action.
static const int RetryWith = 449;

// Internal server error
static const int InternalServerError = 500;
// Request not supported
static const int NotSupported = 501;
// Error response received from gateway
static const int BadGateway = 502;
// Temporarily overloaded
static const int ServiceUnavailable = 503;
// Timed out waiting for gateway
static const int GatewayTimeout = 504;
// HTTP version not supported
static const int VersionNotSupported = 505;
}  // namespace status_code

class Response;  // Just a forward declaration.
class FormData;

///////////////////////////////////////////////////////////////////////////////
// Request class is the main object used to set up and initiate an HTTP
// communication session. It is used to specify the HTTP request method,
// request URL and many optional parameters (such as HTTP headers, user agent,
// referer URL and so on.
//
// Once everything is setup, GetResponse() method is used to send the request
// and obtain the server response. The returned Response object can be
// used to inspect the response code, HTTP headers and/or response body.
///////////////////////////////////////////////////////////////////////////////
class BRILLO_EXPORT Request final {
 public:
  // The main constructor. |url| specifies the remote host address/path
  // to send the request to. |method| is the HTTP request verb and
  // |transport| is the HTTP transport implementation for server communications.
  Request(const std::string& url,
          const std::string& method,
          std::shared_ptr<Transport> transport);
  ~Request();

  // Gets/Sets "Accept:" header value. The default value is "*/*" if not set.
  void SetAccept(const std::string& accept_mime_types);
  const std::string& GetAccept() const;

  // Gets/Sets "Content-Type:" header value
  void SetContentType(const std::string& content_type);
  const std::string& GetContentType() const;

  // Adds additional HTTP request header
  void AddHeader(const std::string& header, const std::string& value);
  void AddHeaders(const HeaderList& headers);

  // Removes HTTP request header
  void RemoveHeader(const std::string& header);

  // Adds a request body. This is not to be used with GET method
  bool AddRequestBody(const void* data, size_t size, brillo::ErrorPtr* error);
  bool AddRequestBody(StreamPtr stream, brillo::ErrorPtr* error);

  // Adds a request body. This is not to be used with GET method.
  // This method also sets the correct content-type of the request, including
  // the multipart data boundary.
  bool AddRequestBodyAsFormData(std::unique_ptr<FormData> form_data,
                                brillo::ErrorPtr* error);

  // Adds a stream for the response. Otherwise a MemoryStream will be used.
  bool AddResponseStream(StreamPtr stream, brillo::ErrorPtr* error);

  // Makes a request for a subrange of data. Specifies a partial range with
  // either from beginning of the data to the specified offset (if |bytes| is
  // negative) or from the specified offset to the end of data (if |bytes| is
  // positive).
  // All individual ranges will be sent as part of "Range:" HTTP request header.
  void AddRange(int64_t bytes);

  // Makes a request for a subrange of data. Specifies a full range with
  // start and end bytes from the beginning of the requested data.
  // All individual ranges will be sent as part of "Range:" HTTP request header.
  void AddRange(uint64_t from_byte, uint64_t to_byte);

  // Returns the request URL
  const std::string& GetRequestURL() const;

  // Returns the request verb.
  const std::string& GetRequestMethod() const;

  // Gets/Sets a request referer URL (sent as "Referer:" request header).
  void SetReferer(const std::string& referer);
  const std::string& GetReferer() const;

  // Gets/Sets a user agent string (sent as "User-Agent:" request header).
  void SetUserAgent(const std::string& user_agent);
  const std::string& GetUserAgent() const;

  // Sends the request to the server and blocks until the response is received,
  // which is returned as the response object.
  // In case the server couldn't be reached for whatever reason, returns
  // empty unique_ptr (null). In such a case, the additional error information
  // can be returned through the optional supplied |error| parameter.
  std::unique_ptr<Response> GetResponseAndBlock(brillo::ErrorPtr* error);

  // Sends out the request and invokes the |success_callback| when the response
  // is received. In case of an error, the |error_callback| is invoked.
  // Returns the ID of the asynchronous request created.
  RequestID GetResponse(const SuccessCallback& success_callback,
                        const ErrorCallback& error_callback);

 private:
  friend class HttpRequestTest;

  // Helper function to create an http::Connection and send off request headers.
  BRILLO_PRIVATE bool SendRequestIfNeeded(brillo::ErrorPtr* error);

  // Implementation that provides particular HTTP transport.
  std::shared_ptr<Transport> transport_;

  // An established connection for adding request body. This connection
  // is maintained by the request object after the headers have been
  // sent and before the response is requested.
  std::shared_ptr<Connection> connection_;

  // Full request URL, such as "http://www.host.com/path/to/object"
  const std::string request_url_;
  // HTTP request verb, such as "GET", "POST", "PUT", ...
  const std::string method_;

  // Referrer URL, if any. Sent to the server via "Referer: " header.
  std::string referer_;
  // User agent string, if any. Sent to the server via "User-Agent: " header.
  std::string user_agent_;
  // Content type of the request body data.
  // Sent to the server via "Content-Type: " header.
  std::string content_type_;
  // List of acceptable response data types.
  // Sent to the server via "Accept: " header.
  std::string accept_ = "*/*";

  // List of optional request headers provided by the caller.
  std::multimap<std::string, std::string> headers_;
  // List of optional data ranges to request partial content from the server.
  // Sent to the server as "Range: " header.
  std::vector<std::pair<uint64_t, uint64_t>> ranges_;

  // range_value_omitted is used in |ranges_| list to indicate omitted value.
  // E.g. range (10,range_value_omitted) represents bytes from 10 to the end
  // of the data stream.
  const uint64_t range_value_omitted = std::numeric_limits<uint64_t>::max();

  DISALLOW_COPY_AND_ASSIGN(Request);
};

///////////////////////////////////////////////////////////////////////////////
// Response class is returned from Request::GetResponse() and is a way
// to get to response status, error codes, response HTTP headers and response
// data (body) if available.
///////////////////////////////////////////////////////////////////////////////
class BRILLO_EXPORT Response final {
 public:
  explicit Response(const std::shared_ptr<Connection>& connection);
  ~Response();

  // Returns true if server returned a success code (status code below 400).
  bool IsSuccessful() const;

  // Returns the HTTP status code (e.g. 200 for success)
  int GetStatusCode() const;

  // Returns the status text (e.g. for error 403 it could be "NOT AUTHORIZED").
  std::string GetStatusText() const;

  // Returns the content type of the response data.
  std::string GetContentType() const;

  // Returns response data stream by transferring ownership of the data stream
  // from Response class to the caller.
  StreamPtr ExtractDataStream(ErrorPtr* error);

  // Extracts the data from the underlying response data stream as a byte array.
  std::vector<uint8_t> ExtractData();

  // Extracts the data from the underlying response data stream as a string.
  std::string ExtractDataAsString();

  // Returns a value of a given response HTTP header.
  std::string GetHeader(const std::string& header_name) const;

 private:
  friend class HttpRequestTest;

  std::shared_ptr<Connection> connection_;

  DISALLOW_COPY_AND_ASSIGN(Response);
};

}  // namespace http
}  // namespace brillo

#endif  // LIBBRILLO_BRILLO_HTTP_HTTP_REQUEST_H_