// 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_FORM_DATA_H_
#define LIBBRILLO_BRILLO_HTTP_HTTP_FORM_DATA_H_

#include <memory>
#include <string>
#include <vector>

#include <base/files/file_path.h>
#include <base/macros.h>
#include <brillo/brillo_export.h>
#include <brillo/errors/error.h>
#include <brillo/streams/stream.h>

namespace brillo {
namespace http {

namespace content_disposition {
BRILLO_EXPORT extern const char kFormData[];
BRILLO_EXPORT extern const char kFile[];
}  // namespace content_disposition

// An abstract base class for all types of form fields used by FormData class.
// This class represents basic information about a form part in
// multipart/form-data and multipart/mixed content.
// For more details on multipart content, see the following RFC:
//    http://www.ietf.org/rfc/rfc2388
// For more details on MIME and content headers, see the following RFC:
//    http://www.ietf.org/rfc/rfc2045
class BRILLO_EXPORT FormField {
 public:
  // The constructor that takes the basic data part information common to
  // all part types. An example of part's headers could include:
  //
  //    Content-Disposition: form-data; name="field1"
  //    Content-Type: text/plain;charset=windows-1250
  //    Content-Transfer-Encoding: quoted-printable
  //
  // The constructor parameters correspond to the basic part attributes:
  //  |name| = the part name ("name" parameter of Content-Disposition header;
  //           "field1" in the example above)
  //  |content_disposition| = the part disposition ("form-data" in the example)
  //  |content_type| = the content type ("text/plain;charset=windows-1250")
  //  |transfer_encoding| = the encoding type for transport ("quoted-printable")
  //                        See http://www.ietf.org/rfc/rfc2045, section 6.1
  FormField(const std::string& name,
            const std::string& content_disposition,
            const std::string& content_type,
            const std::string& transfer_encoding);
  virtual ~FormField() = default;

  // Returns the full Content-Disposition header value. This might include the
  // disposition type itself as well as the field "name" and/or "filename"
  // parameters.
  virtual std::string GetContentDisposition() const;

  // Returns the full content type of field data. MultiPartFormField overloads
  // this method to append "boundary" parameter to it.
  virtual std::string GetContentType() const;

  // Returns a string with all of the field headers, delimited by CRLF
  // characters ("\r\n").
  std::string GetContentHeader() const;

  // Adds the data stream(s) to the list of streams to read from.
  // This is a potentially destructive operation and can be guaranteed to
  // succeed only on the first try. Subsequent calls will fail for certain
  // types of form fields.
  virtual bool ExtractDataStreams(std::vector<StreamPtr>* streams) = 0;

 protected:
  // Form field name. If not empty, it will be appended to Content-Disposition
  // field header using "name" attribute.
  std::string name_;

  // Form field disposition. Most of the time this will be "form-data". But for
  // nested file uploads inside "multipart/mixed" sections, this can be "file".
  std::string content_disposition_;

  // Content type. If omitted (empty), "plain/text" assumed.
  std::string content_type_;

  // Transfer encoding for field data. If omitted, "7bit" is assumed. For most
  // binary contents (e.g. for file content), use "binary".
  std::string transfer_encoding_;

 private:
  DISALLOW_COPY_AND_ASSIGN(FormField);
};

// Simple text form field.
class BRILLO_EXPORT TextFormField : public FormField {
 public:
  // Constructor. Parameters:
  //  name: field name
  //  data: field text data
  //  content_type: the data content type. Empty if not specified.
  //  transfer_encoding: the encoding type of data. If omitted, no encoding
  //      is specified (and "7bit" is assumed).
  TextFormField(const std::string& name,
                const std::string& data,
                const std::string& content_type = {},
                const std::string& transfer_encoding = {});

  bool ExtractDataStreams(std::vector<StreamPtr>* streams) override;

 private:
  std::string data_;  // Buffer/reader for field data.

  DISALLOW_COPY_AND_ASSIGN(TextFormField);
};

// File upload form field.
class BRILLO_EXPORT FileFormField : public FormField {
 public:
  // Constructor. Parameters:
  //  name: field name
  //  stream: open stream with the contents of the file.
  //  file_name: just the base file name of the file, e.g. "file.txt".
  //      Used in "filename" parameter of Content-Disposition header.
  //  content_type: valid content type of the file.
  //  transfer_encoding: the encoding type of data.
  //      If omitted, "binary" is used.
  FileFormField(const std::string& name,
                StreamPtr stream,
                const std::string& file_name,
                const std::string& content_disposition,
                const std::string& content_type,
                const std::string& transfer_encoding = {});

  // Override from FormField.
  // Appends "filename" parameter to Content-Disposition header.
  std::string GetContentDisposition() const override;

  bool ExtractDataStreams(std::vector<StreamPtr>* streams) override;

 private:
  StreamPtr stream_;
  std::string file_name_;

  DISALLOW_COPY_AND_ASSIGN(FileFormField);
};

// Multipart form field.
// This is used directly by FormData class to build the request body for
// form upload. It can also be used with multiple file uploads for a single
// file field, when the uploaded files should be sent as "multipart/mixed".
class BRILLO_EXPORT MultiPartFormField : public FormField {
 public:
  // Constructor. Parameters:
  //  name: field name
  //  content_type: valid content type. If omitted, "multipart/mixed" is used.
  //  boundary: multipart boundary separator.
  //      If omitted/empty, a random string is generated.
  explicit MultiPartFormField(const std::string& name,
                     const std::string& content_type = {},
                     const std::string& boundary = {});

  // Override from FormField.
  // Appends "boundary" parameter to Content-Type header.
  std::string GetContentType() const override;

  bool ExtractDataStreams(std::vector<StreamPtr>* streams) override;

  // Adds a form field to the form data. The |field| could be a simple text
  // field, a file upload field or a multipart form field.
  void AddCustomField(std::unique_ptr<FormField> field);

  // Adds a simple text form field.
  void AddTextField(const std::string& name, const std::string& data);

  // Adds a file upload form field using a file path.
  bool AddFileField(const std::string& name,
                    const base::FilePath& file_path,
                    const std::string& content_disposition,
                    const std::string& content_type,
                    brillo::ErrorPtr* error);

  // Returns a boundary string used to separate multipart form fields.
  const std::string& GetBoundary() const { return boundary_; }

 private:
  // Returns the starting boundary string: "--<boundary>".
  std::string GetBoundaryStart() const;
  // Returns the ending boundary string: "--<boundary>--".
  std::string GetBoundaryEnd() const;

  std::string boundary_;  // Boundary string used as field separator.
  std::vector<std::unique_ptr<FormField>> parts_;  // Form field list.

  DISALLOW_COPY_AND_ASSIGN(MultiPartFormField);
};

// A class representing a multipart form data for sending as HTTP POST request.
class BRILLO_EXPORT FormData final {
 public:
  FormData();
  // Allows to specify a custom |boundary| separator string.
  explicit FormData(const std::string& boundary);

  // Adds a form field to the form data. The |field| could be a simple text
  // field, a file upload field or a multipart form field.
  void AddCustomField(std::unique_ptr<FormField> field);

  // Adds a simple text form field.
  void AddTextField(const std::string& name, const std::string& data);

  // Adds a file upload form field using a file path.
  bool AddFileField(const std::string& name,
                    const base::FilePath& file_path,
                    const std::string& content_type,
                    brillo::ErrorPtr* error);

  // Returns the complete content type string to be used in HTTP requests.
  std::string GetContentType() const;

  // Returns the data stream for the form data. This is a potentially
  // destructive operation and can be called only once.
  StreamPtr ExtractDataStream();

 private:
  MultiPartFormField form_data_;

  DISALLOW_COPY_AND_ASSIGN(FormData);
};

}  // namespace http
}  // namespace brillo

#endif  // LIBBRILLO_BRILLO_HTTP_HTTP_FORM_DATA_H_