/*
    GSK - a library to write servers
    Copyright (C) 1999-2000 Dave Benson

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA

    Contact:
        daveb@ffem.org <Dave Benson>
*/

/*
 * As per the HTTP 1.1 Specification, RFC 2616.
 *
 * TODO:  Compliance notes.
 */

#ifndef __GSK_HTTP_HEADER_H_
#define __GSK_HTTP_HEADER_H_

#include <glib.h>
#include "../gskbuffer.h"

typedef struct _GskHttpHeader GskHttpHeader;
typedef struct _GskHttpHeaderCookie GskHttpHeaderCookie;
typedef struct _GskHttpAuthorization GskHttpAuthorization;
typedef struct _GskHttpAuthenticate GskHttpAuthenticate;
typedef struct _GskHttpCharSet GskHttpCharSet;
typedef struct _GskHttpCacheDirective GskHttpCacheDirective;
typedef struct _GskHttpLanguageSet GskHttpLanguageSet;

typedef enum
{
  GSK_HTTP_STATUS_CONTINUE                      = 100,
  GSK_HTTP_STATUS_SWITCHING_PROTOCOLS           = 101,
  GSK_HTTP_STATUS_OK                            = 200,
  GSK_HTTP_STATUS_CREATED                       = 201,
  GSK_HTTP_STATUS_ACCEPTED                      = 202,
  GSK_HTTP_STATUS_NONAUTHORITATIVE_INFO         = 203,
  GSK_HTTP_STATUS_NO_CONTENT                    = 204,
  GSK_HTTP_STATUS_RESET_CONTENT                 = 205,
  GSK_HTTP_STATUS_PARTIAL_CONTENT               = 206,
  GSK_HTTP_STATUS_MULTIPLE_CHOICES              = 300,
  GSK_HTTP_STATUS_MOVED_PERMANENTLY             = 301,
  GSK_HTTP_STATUS_FOUND                         = 302,
  GSK_HTTP_STATUS_SEE_OTHER                     = 303,
  GSK_HTTP_STATUS_NOT_MODIFIED                  = 304,
  GSK_HTTP_STATUS_USE_PROXY                     = 305,
  GSK_HTTP_STATUS_TEMPORARY_REDIRECT            = 306,
  GSK_HTTP_STATUS_BAD_REQUEST                   = 400,
  GSK_HTTP_STATUS_UNAUTHORIZED                  = 401,
  GSK_HTTP_STATUS_PAYMENT_REQUIRED              = 402,
  GSK_HTTP_STATUS_FORBIDDEN                     = 403,
  GSK_HTTP_STATUS_NOT_FOUND                     = 404,
  GSK_HTTP_STATUS_METHOD_NOT_ALLOWED            = 405,
  GSK_HTTP_STATUS_NOT_ACCEPTABLE                = 406,
  GSK_HTTP_STATUS_PROXY_AUTH_REQUIRED           = 407,
  GSK_HTTP_STATUS_REQUEST_TIMEOUT               = 408,
  GSK_HTTP_STATUS_CONFLICT                      = 409,
  GSK_HTTP_STATUS_GONE                          = 410,
  GSK_HTTP_STATUS_LENGTH_REQUIRED               = 411,
  GSK_HTTP_STATUS_PRECONDITION_FAILED           = 412,
  GSK_HTTP_STATUS_ENTITY_TOO_LARGE              = 413,
  GSK_HTTP_STATUS_URI_TOO_LARGE                 = 414,
  GSK_HTTP_STATUS_UNSUPPORTED_MEDIA             = 415,
  GSK_HTTP_STATUS_BAD_RANGE                     = 416,
  GSK_HTTP_STATUS_EXPECTATION_FAILED            = 417,
  GSK_HTTP_STATUS_INTERNAL_SERVER_ERROR         = 500,
  GSK_HTTP_STATUS_NOT_IMPLEMENTED               = 501,
  GSK_HTTP_STATUS_BAD_GATEWAY                   = 502,
  GSK_HTTP_STATUS_SERVICE_UNAVAILABLE           = 503,
  GSK_HTTP_STATUS_GATEWAY_TIMEOUT               = 504,
  GSK_HTTP_STATUS_UNSUPPORTED_VERSION           = 505
} GskHttpStatus;

/*
 * The Verb is the first text transmitted
 * (from the user-agent to the server).
 */
typedef enum
{
  GSK_HTTP_VERB_GET,
  GSK_HTTP_VERB_POST,
  GSK_HTTP_VERB_PUT,
  GSK_HTTP_VERB_HEAD,
  GSK_HTTP_VERB_OPTIONS,
  GSK_HTTP_VERB_DELETE,
  GSK_HTTP_VERB_TRACE,
  GSK_HTTP_VERB_CONNECT
} GskHttpVerb;

/* A GskHttpRange is a unit in which partial content ranges
 * may be specified and transferred.
 */
typedef enum _GskHttpRange
{
  GSK_HTTP_RANGE_BYTES
} GskHttpRange;

/*
 * The Transfer-Encoding field of HTTP/1.1.
 *
 * In particular, HTTP/1.1 compliant clients and proxies
 * MUST support `chunked'.  The compressed Transfer-Encodings
 * are rarely (if ever) used.
 *
 * Note that:
 *   - we interpret this field, even for HTTP/1.0 clients.
 *   - we don't support gzip, but we should.
 */
typedef enum {
  GSK_HTTP_ENCODING_NONE    = 0,
  GSK_HTTP_ENCODING_CHUNKED = 1,
  GSK_HTTP_ENCODING_UNRECOGNIZED = 0x100
} GskHttpEncoding;

/*
 * The Connection: header enables or disables http-keepalive.
 *
 * For HTTP/1.0, NONE should be treated like CLOSE.
 * For HTTP/1.1, NONE should be treated like KEEPALIVE.
 *
 * Use gsk_http_header_get_connection () to do this automatically -- it
 * always returns GSK_HTTP_CONNECTION_CLOSE or GSK_HTTP_CONNECTION_KEEPALIVE.
 *
 * See RFC 2616, Section 14.10.
 */
typedef enum
{
  GSK_HTTP_CONNECTION_NONE       = 0,
  GSK_HTTP_CONNECTION_CLOSE      = 1,
  GSK_HTTP_CONNECTION_KEEPALIVE  = 2,
} GskHttpConnection;

/*
 * The Cache-Control directive.
 * See RFC 2616, Section 14.9
 */
struct _GskHttpCacheDirective
{
  unsigned              no_cache : 1,
                        no_store : 1,
                        no_transform : 1,

                        /* the http is `public' and `private'; is_ is added
                         * for C++ users */
                        is_public : 1,
                        is_private : 1,
                        only_if_cached : 1,

                        must_revalidate : 1,
                        proxy_revalidate : 1;
  int                   max_age;
  int                   smax_age;
  int                   max_stale;
  int                   min_fresh;
  int                   s_max_age;

  char                 *private_name;
  char                 *no_cache_name;
};

/*
 * The Accept: request-header.
 *
 * See RFC 2616, Section 14.1.
 */
typedef struct _GskHttpMediaTypeSet GskHttpMediaTypeSet;
struct _GskHttpMediaTypeSet
{
  char                 *type;
  char                 *subtype;
  gfloat                quality;                /* -1 if not present */
  GskHttpMediaTypeSet  *next;
};

typedef struct _GskHttpEncodingSet GskHttpEncodingSet;
typedef struct _GskHttpRangeSet GskHttpRangeSet;

/*
 * The Accept-Charset: request-header.
 *
 * See RFC 2616, Section 14.2.
 */
struct _GskHttpCharSet
{
  char                 *charset_name;
  gfloat                quality;                /* -1 if not present */
  GskHttpCharSet       *next;
};

/*
 * The Accept-Encoding: request-header.
 *
 * See RFC 2616, Section 14.3.
 */
struct _GskHttpEncodingSet
{
  GskHttpEncoding       encoding;
  gfloat                quality;                /* -1 if not present */
  GskHttpEncodingSet   *next;
};


struct _GskHttpRangeSet
{
  GskHttpRange          range_type;
  GskHttpEncodingSet   *next;
};

/*
 * The Accept-Language: request-header.
 *
 * See RFC 2616, Section 14.4.
 */
struct _GskHttpLanguageSet
{
  char                 *language;
  gfloat                quality;                /* -1 if not present */
  GskHttpLanguageSet   *next;
};

struct _GskHttpAuthenticate
{
  char                   *challenge;
};

struct _GskHttpAuthorization
{
  char                   *credentials;
};

/* A single `Cookie' or `Set-Cookie' header.
 *
 * See RFC 2109, HTTP State Management Mechanism 
 */
struct _GskHttpHeaderCookie
{
  char                   *key;
  char                   *value;
  char                   *domain;
  char                   *path;
  char                   *date_string;
  char                   *comment;
  int                     max_age;
};

/*
 *                 GskHttpHeader
 *
 * A structure embodying an http header
 * (as in a request or a response).
 */
struct _GskHttpHeader
{
  int			        ref_count;
  /*
   * Is this a request or a response header?
   */
  gboolean		        is_request;

  /*---------------------------------------------------------------------
   * The first-line of Header
   *
   * This union and `http_minor_version' describe the
   * first line of the HTTP header.
   *     e.g.     GET /index.html HTTP/1.1
   * sets:
   *       `request.verb'             == GSK_HTTP_VERB_GET
   *       `request.path'             == /index.html
   *       `http_minor_version'       == 1
   */
  union
  {
    struct
    {
      GskHttpVerb               verb;
      /* Note that HTTP/1.1 servers must accept the entire
       * URL being included in `path'! */
      char                     *path;
    } request;
    struct
    {
      GskHttpStatus             status_code;
    } response;
  } first_line;

  /* 0 for HTTP/1.0; 1 for HTTP/1.1. */
  int			        http_minor_version;

  /*---------------------------------------------------------------------
   * General headers.
   */

  /* Connection: headers. */
  GskHttpConnection	        connection_type;

  /* Transfer-Encoding: encoding. */
  GskHttpEncoding	        encoding_type;
  char                         *unrecognized_encoding;

  /* List of Cookie: headers. */
  GList                        *cookies;

  /* From the Content-Length header. */
  int			        content_length;

  /* Key/value searchable header lines. */
  GHashTable                   *header_lines;
  GskHttpCacheDirective        *cache_control;        /* Cache-Control */

  /*---------------------------------------------------------------------
   * Error messages.
   */
  GSList                       *errors;


  /*---------------------------------------------------------------------
   * General headers.
   */
  GSList                       *pragmas;

  /*---------------------------------------------------------------------
   * Entity-Headers.
   * These are affiliated with the message body (the entity).
   */

  /* Bytes ranges. Both with be == -1 if there is no Range tag. */
  int                           range_start;
  int                           range_end;

  /* Request- or Response- Specific Headers */
  union
  {
    /*---------------------------------------------------------------------
     * Request-Specific Headers.
     */
    struct
    {
      GskHttpCharSet           *accept_charsets;      /* Accept-CharSet */
      GskHttpEncodingSet       *accept_encodings;     /* Accept-Encoding */
      GskHttpRangeSet          *accept_ranges;        /* Accept-Ranges */
      GskHttpMediaTypeSet      *accept_media_types;   /* Accept */
      GskHttpAuthorization      authorization;        /* Authorization */
      char                     *host;                 /* Host */

      gboolean                  had_if_match;
      char                    **if_match;             /* If-Match */
      time_t                    if_modified_since;    /* If-Modified-Since */
      char                     *user_agent;           /* User-Agent */

      char                     *referrer;             /* Referer */

      GskHttpAuthorization      proxy_auth;

      /* rarely used: */
      int                       max_forwards;         /* -1 if not used */
    } request;

    /*---------------------------------------------------------------------
     * Response-Specific Headers.
     */
    struct
    {
      int                       age;                  /* Age */

      /* initially allowed_verbs == 0;
       * since it is an error not to allow any verbs;
       * otherwise it is a bitwise-OR: (1 << GSK_HTTP_VERB_*)
       */
      guint                     allowed_verbs;

      char                     *content_encoding;     /* Content-Encoding */

      unsigned                  has_md5sum : 1;
      unsigned                  has_content_type : 1;

      unsigned char             md5sum[16];           /* Content-MD5 (14.15) */

      char                     *content_type;	      /* Content-Type */
      char                     *content_subtype;
      char                     *content_charset;
      GSList                   *content_additional;

      /* The `Location' to redirect to. */
      char                     *location;

      /* These may be set to ((time_t) -1) to omit them. */
      time_t                    date;
      time_t                    expires;

      /* The ``Entity-Tag'', cf RFC 2616, Sections 14.24, 14.26, 14.44. */
      char                     *etag;

      char                     *from;      /* The From: header (sect 14.22) */

      GskHttpAuthenticate       proxy_auth;

      /* If `retry_after_relative', the retry_after is the number 
       * of seconds to wait before retrying; otherwise,
       * it is a unix-time indicting when to retry.
       *
       * (Corresponding to the `Retry-After' header, cf RFC 2616, 14.37)
       */
      guint                     has_retry_after : 1;
      gboolean                  retry_after_relative;
      time_t                    retry_after;

      /* The Last-Modified header.  If != -1, this is the unix-time
       * the message-body-contents were last modified. (RFC 2616, section 14.29)
       */
      time_t                    last_modified;

      char                     *server;        /* The Server: header */
    } response;
  } info;
};

GskHttpConnection    gsk_http_header_get_connection (GskHttpHeader *header);

/* Make a new request will all NULL stuff... */
GskHttpHeader       *gsk_http_header_new          (gboolean       is_request);

/* Make a http-header response that is appropriate for
 * a given request, optimizing to use Keep-Alive if possible.
 *
 * `length == -1' means that the lentgh is indeterminate.
 */
GskHttpHeader       *gsk_http_header_new_response (GskHttpHeader *header,
                                                   int            length,
                                                   GskHttpStatus  code);

/* Public methods to parse/write a header. */
typedef enum
{
  GSK_HTTP_PARSE_STRICT = (1 << 0)
} GskHttpParseFlags;

gboolean             gsk_http_header_test_buffer  (GskBuffer     *buffer,
                                                   int            tested_len);
GskHttpHeader       *gsk_http_header_from_buffer  (GskBuffer     *buffer,
                                                   gboolean       is_request,
						   GskHttpParseFlags flags);
void                 gsk_http_header_to_buffer    (GskHttpHeader *http_header,
                                                   GskBuffer     *buffer);

/* Public method to ref/unref a header. */
void		     gsk_http_header_ref          (GskHttpHeader *header);
void		     gsk_http_header_unref        (GskHttpHeader *header);

/* --- cookies --- */
GskHttpHeaderCookie *gsk_http_header_cookie_new   (const char    *key,
			                           const char	 *value,
						   const char    *path,
                                                   const char    *domain,
			                           const char	 *expire_date,
			                           const char	 *comment,
			                           int		  max_age);
GskHttpHeaderCookie *gsk_http_header_find_cookie  (GskHttpHeader *header,
                                                   const char    *key);
GskHttpHeaderCookie *gsk_http_header_cookie_copy  (GskHttpHeaderCookie *orig);

/* --- accessor methods --- */
void                 gsk_http_header_set_verb       (GskHttpHeader *header,
						     GskHttpVerb    verb);
GskHttpVerb          gsk_http_header_get_verb       (GskHttpHeader *header);
void                 gsk_http_header_set_path       (GskHttpHeader *header,
						     const char    *path);
const char          *gsk_http_header_get_path       (GskHttpHeader *header);
void                 gsk_http_header_set_status_code(GskHttpHeader *header,
						     const char    *path);
const char          *gsk_http_header_get_status_code(GskHttpHeader *header);
void                 gsk_http_header_set_minor_version(GskHttpHeader *header,
						     int            minor_v);
int                  gsk_http_header_get_minor_version(GskHttpHeader *header);
void                 gsk_http_header_set_conn_type  (GskHttpHeader *header,
						     GskHttpConnection type);
GskHttpConnection    gsk_http_header_get_conn_type  (GskHttpHeader *header);
void                 gsk_http_header_set_encoding   (GskHttpHeader *header,
						     GskHttpEncoding type);
void                 gsk_http_header_set_encoding_s (GskHttpHeader *header,
						     const char    *encoding);
GskHttpEncoding      gsk_http_header_get_encoding   (GskHttpHeader *header);
void                 gsk_http_header_set_content_len(GskHttpHeader *header,
						     gint           length);
gint                 gsk_http_header_get_content_len(GskHttpHeader *header);
void                 gsk_http_header_set_range      (GskHttpHeader *header,
						     guint          range_start,
						     guint          range_end);
void                 gsk_http_header_set_no_range   (GskHttpHeader *header);
gboolean             gsk_http_header_get_range      (GskHttpHeader *header,
						     guint         *range_start,
						     guint         *range_end);
/* unhandled: errors, pragmas */

/* request specific functions */
/* unhandled: accept_ranges, if_match */
GskHttpCharSet      *gsk_http_header_get_charsets   (GskHttpHeader *header);
GskHttpCharSet      *gsk_http_header_add_charsets   (GskHttpHeader *header,
						     GskHttpCharSet *char_sets);
void                 gsk_http_header_clear_charsets (GskHttpHeader *header);
GskHttpEncodingSet  *gsk_http_header_get_encodings  (GskHttpHeader *header);
GskHttpEncodingSet  *gsk_http_header_add_encodings  (GskHttpHeader *header,
						     GskHttpEncodingSet *set);
void                 gsk_http_header_clear_encodings(GskHttpHeader *header);
GskHttpMediaTypeSet *gsk_http_header_get_media      (GskHttpHeader *header);
GskHttpMediaTypeSet *gsk_http_header_add_media      (GskHttpHeader *header,
						     GskHttpMediaTypeSet *set);
void                 gsk_http_header_clear_media    (GskHttpHeader *header);
void                 gsk_http_header_set_authorization(GskHttpHeader *header,
						     GskHttpAuthorization *);
GskHttpAuthorization*gsk_http_header_get_authorization(GskHttpHeader *header);

void                 gsk_http_header_set_host       (GskHttpHeader *header,
						     const char    *host);
const char          *gsk_http_header_get_host       (GskHttpHeader *header);
void                 gsk_http_header_set_user_agent (GskHttpHeader *header,
						     const char    *user_agent);
const char          *gsk_http_header_get_user_agent (GskHttpHeader *header);
void                 gsk_http_header_set_referrer   (GskHttpHeader *header,
						     const char    *referrer);
const char          *gsk_http_header_get_referrer   (GskHttpHeader *header);
time_t         gsk_http_header_get_if_modified_since(GskHttpHeader *header);
void           gsk_http_header_set_if_modified_since(GskHttpHeader *header,
						     time_t         ims);
GskHttpAuthorization
            gsk_http_header_get_proxy_authorization (GskHttpHeader *header);
void        gsk_http_header_set_proxy_authorization (GskHttpHeader *header,
						     GskHttpAuthorization auth);

/* response specific functions */
/* unhandled: content-type and friends */
void                 gsk_http_header_set_age        (GskHttpHeader *header,
						     int            age);
int                  gsk_http_header_get_age        (GskHttpHeader *header);
void               gsk_http_header_set_allowed_verbs(GskHttpHeader *header,
						     guint          allowed);
guint              gsk_http_header_get_allowed_verbs(GskHttpHeader *header);
void            gsk_http_header_set_content_encoding(GskHttpHeader *header,
						     const char      *encoding);
const char     *gsk_http_header_get_content_encoding(GskHttpHeader*header);

/* md5sum may be NULL to unset it */
void                 gsk_http_header_set_md5        (GskHttpHeader *header,
						     const guint8  *md5sum);
const guint8        *gsk_http_header_get_md5        (GskHttpHeader *header);
      /*content_type; content_subtype; content_charset; content_additional;*/
void                 gsk_http_header_set_location   (GskHttpHeader *header,
						     const char    *location);
const char          *gsk_http_header_get_location   (GskHttpHeader *header);
void                 gsk_http_header_set_date       (GskHttpHeader *header,
						     time_t         date);
time_t               gsk_http_header_get_date       (GskHttpHeader *header);
void                 gsk_http_header_set_expires    (GskHttpHeader *header,
						     time_t         expires);
time_t               gsk_http_header_get_expires    (GskHttpHeader *header);
void                 gsk_http_header_set_etag       (GskHttpHeader *header,
						     const char    *etag);
const char          *gsk_http_header_get_etag       (GskHttpHeader *header);
void                 gsk_http_header_set_from       (GskHttpHeader *header,
						     const char    *from);
const char          *gsk_http_header_get_from       (GskHttpHeader *header);
void          gsk_http_header_set_proxy_authenticate(GskHttpHeader *header,
						     GskHttpAuthenticate auth);
GskHttpAuthenticate
              gsk_http_header_get_proxy_authenticate(GskHttpHeader *header);
void                 gsk_http_header_set_retry_after(GskHttpHeader *header,
						     gboolean       is_relative,
						     time_t         time);
void              gsk_http_header_set_no_retry_after(GskHttpHeader *header);
gboolean             gsk_http_header_get_retry_after(GskHttpHeader *header,
						     gboolean      *is_relative,
						     time_t        *time);
void               gsk_http_header_set_last_modified(GskHttpHeader *header,
						     time_t         last_mod);
time_t             gsk_http_header_get_last_modified(GskHttpHeader *header);
void               gsk_http_header_set_server       (GskHttpHeader *header,
						     const char    *server);
const char        *gsk_http_header_get_server       (GskHttpHeader *header);

/* --- miscellaneous key/value pairs --- */
void                 gsk_http_header_add_misc     (GskHttpHeader *header,
                                                   const char    *key,
			                           const char    *value);
void                 gsk_http_header_remove_misc  (GskHttpHeader *header,
                                                   const char    *key);

char                *gsk_http_header_gen_random_cookie();
/* Probably doesn't need to be published, so it isn't. 
void                 gsk_http_header_cookie_delete(GskHttpHeaderCookie* cookie);
*/

/* Standard header constructions... */

/* Responses. */
/* Redirects should be accompanied by an HTML body
 * saying the URL. */
GskHttpHeader       *gsk_http_header_new_redirect (const char    *location);

#endif
