#include "gskhttpheader.h"
#include "gskhttprequest.h"
#include "gskhttpresponse.h"
#include "../gskmacros.h"
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

static GObjectClass *parent_class = NULL;

enum
{
  PROP_RESPONSE_0,
  PROP_RESPONSE_STATUS_CODE,
  PROP_RESPONSE_AGE,
  PROP_RESPONSE_LOCATION,
  PROP_RESPONSE_EXPIRES,
  PROP_RESPONSE_ETAG,
  PROP_RESPONSE_LAST_MODIFIED,
  PROP_RESPONSE_SERVER
};

/* --- GskHttpResponse implementation --- */
/**
 * gsk_http_response_has_content_body:
 * @response: the response to query
 * @request: the request that provoked the response.
 *
 * Find out whether a HTTP Response should be
 * accompanied by a body.
 *
 * It also depends on the request, in particular,
 * HEAD requests do not retrieve bodies.
 *
 * returns: whether the response should be accompanied
 * by a body of data.
 */
gboolean
gsk_http_response_has_content_body (GskHttpResponse *response,
                                    GskHttpRequest  *request)
{
  if (request->verb == GSK_HTTP_VERB_HEAD)
    return FALSE;
  switch (response->status_code)
    {
	/* Status-codes that don't generally include entity body. */
	case GSK_HTTP_STATUS_CONTINUE:
	case GSK_HTTP_STATUS_SWITCHING_PROTOCOLS:
	case GSK_HTTP_STATUS_CREATED:
	case GSK_HTTP_STATUS_ACCEPTED:
	case GSK_HTTP_STATUS_NO_CONTENT:
	case GSK_HTTP_STATUS_RESET_CONTENT:
	case GSK_HTTP_STATUS_NOT_MODIFIED:
          return FALSE;

	case GSK_HTTP_STATUS_OK:
	case GSK_HTTP_STATUS_NONAUTHORITATIVE_INFO:
	case GSK_HTTP_STATUS_MULTIPLE_CHOICES:
          if (request->verb == GSK_HTTP_VERB_PUT
           || request->verb == GSK_HTTP_VERB_DELETE) /* HEAD handled above */
            return FALSE;
          else
            return TRUE;
	  switch (request->verb)
	    {
	    case GSK_HTTP_VERB_HEAD:
	    case GSK_HTTP_VERB_PUT:
	    case GSK_HTTP_VERB_DELETE:
	      return FALSE;
	    default:
	      return TRUE;
	    }
	  break;

	  /* Must contain entity for all verbs except HEAD. */
	case GSK_HTTP_STATUS_PARTIAL_CONTENT:
	case GSK_HTTP_STATUS_MOVED_PERMANENTLY:
	case GSK_HTTP_STATUS_FOUND:
	case GSK_HTTP_STATUS_SEE_OTHER:
	case GSK_HTTP_STATUS_USE_PROXY:
	case GSK_HTTP_STATUS_TEMPORARY_REDIRECT:
	case GSK_HTTP_STATUS_BAD_REQUEST:
	case GSK_HTTP_STATUS_UNAUTHORIZED:
	case GSK_HTTP_STATUS_PAYMENT_REQUIRED:
	case GSK_HTTP_STATUS_FORBIDDEN:
	case GSK_HTTP_STATUS_NOT_FOUND:
	case GSK_HTTP_STATUS_METHOD_NOT_ALLOWED:
	case GSK_HTTP_STATUS_NOT_ACCEPTABLE:
	case GSK_HTTP_STATUS_PROXY_AUTH_REQUIRED:
	case GSK_HTTP_STATUS_REQUEST_TIMEOUT:
	case GSK_HTTP_STATUS_CONFLICT:
	case GSK_HTTP_STATUS_GONE:
	case GSK_HTTP_STATUS_LENGTH_REQUIRED:
	case GSK_HTTP_STATUS_PRECONDITION_FAILED:
	case GSK_HTTP_STATUS_ENTITY_TOO_LARGE:
	case GSK_HTTP_STATUS_URI_TOO_LARGE:
	case GSK_HTTP_STATUS_UNSUPPORTED_MEDIA:
	case GSK_HTTP_STATUS_BAD_RANGE:
	case GSK_HTTP_STATUS_EXPECTATION_FAILED:
	case GSK_HTTP_STATUS_INTERNAL_SERVER_ERROR:
	case GSK_HTTP_STATUS_NOT_IMPLEMENTED:
	case GSK_HTTP_STATUS_BAD_GATEWAY:
	case GSK_HTTP_STATUS_SERVICE_UNAVAILABLE:
	case GSK_HTTP_STATUS_GATEWAY_TIMEOUT:
	case GSK_HTTP_STATUS_UNSUPPORTED_VERSION:
	  return TRUE;  /* HEAD handled above */
    }
  g_warning ("gsk_http_response_has_content_body: unknown status code %u",
             response->status_code);
  return FALSE;
}

static void
gsk_http_response_set_property  (GObject        *object,
                                 guint           property_id,
                                 const GValue   *value,
                                 GParamSpec     *pspec)
{
  GskHttpResponse *response = GSK_HTTP_RESPONSE (object);
  switch (property_id)
    {
    case PROP_RESPONSE_STATUS_CODE:
      response->status_code = g_value_get_enum (value);
      break;
    case PROP_RESPONSE_AGE:
      response->age = g_value_get_long (value);
      break;
    case PROP_RESPONSE_LOCATION:
      gsk_http_header_set_string_val (response, &response->location, value);
      break;
    case PROP_RESPONSE_EXPIRES:
      response->expires = g_value_get_long (value);
      break;
    case PROP_RESPONSE_ETAG:
      gsk_http_header_set_string_val (response, &response->etag, value);
      break;
    case PROP_RESPONSE_LAST_MODIFIED:
      response->last_modified = g_value_get_long (value);
      break;
    case PROP_RESPONSE_SERVER:
      gsk_http_header_set_string_val (response, &response->server, value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gsk_http_response_get_property  (GObject        *object,
                                 guint           property_id,
                                 GValue         *value,
                                 GParamSpec     *pspec)
{
  GskHttpResponse *response = GSK_HTTP_RESPONSE (object);
  switch (property_id)
    {
    case PROP_RESPONSE_STATUS_CODE:
      g_value_set_enum (value, response->status_code);
      break;
    case PROP_RESPONSE_AGE:
      g_value_set_long (value, response->age);
      break;
#if 0
    case PROP_RESPONSE_CONTENT_ENCODING:
      g_value_set_string (value, response->content_encoding);
      break;
#endif
    case PROP_RESPONSE_LOCATION:
      g_value_set_string (value, response->location);
      break;
    case PROP_RESPONSE_EXPIRES:
      g_value_set_long (value, response->expires);
      break;
    case PROP_RESPONSE_ETAG:
      g_value_set_string (value, response->etag);
      break;
    case PROP_RESPONSE_LAST_MODIFIED:
      g_value_set_long (value, response->last_modified);
      break;
    case PROP_RESPONSE_SERVER:
      g_value_set_string (value, response->server);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gsk_http_response_finalize (GObject *object)
{
  GskHttpResponse *response = GSK_HTTP_RESPONSE (object);
  gsk_http_header_free_string (response, response->location);
  gsk_http_header_free_string (response, response->etag);
  gsk_http_header_free_string (response, response->server);
  if (response->cache_control)
    {
      gsk_http_response_cache_directive_free (response->cache_control);
    }
  if (response->set_cookies)
    {
      g_slist_foreach (response->set_cookies, (GFunc) gsk_http_cookie_free, NULL);
      g_slist_free (response->set_cookies);
    }
  parent_class->finalize (object);
}

static void
gsk_http_response_init (GskHttpResponse *response)
{
  response->status_code = GSK_HTTP_STATUS_OK;
  response->age = -1;
  response->expires = (time_t) -1;
  response->last_modified = (time_t) -1;
}

static void
gsk_http_response_class_init (GObjectClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  parent_class = g_type_class_peek_parent (class);
  class->finalize = gsk_http_response_finalize;
  class->get_property = gsk_http_response_get_property;
  class->set_property = gsk_http_response_set_property;

  g_object_class_install_property (object_class,
                                   PROP_RESPONSE_STATUS_CODE,
                                   g_param_spec_enum ("status-code",
						      _("Status Code"),
						      _("HTTP status code"),
						      GSK_TYPE_HTTP_STATUS,
						      GSK_HTTP_STATUS_OK,
						      G_PARAM_READWRITE));

  g_object_class_install_property (object_class,
                                   PROP_RESPONSE_AGE,
                                   g_param_spec_long ("age",
						      _("Age"),
						      _("Age of content"),
						      -1, G_MAXLONG,
						      -1,
						      G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
                                   PROP_RESPONSE_LOCATION,
                                   g_param_spec_string ("location",
						      _("Location of the content"),
						      _("The URL of the content, for redirect responses"),
						      NULL,
						      G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
                                   PROP_RESPONSE_EXPIRES,
                                   g_param_spec_long ("expires",
						      _("expires"),
						      _("Length of time to wait for content to expire"),
						      -1, G_MAXLONG,
						      -1,
						      G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
                                   PROP_RESPONSE_ETAG,
                                   g_param_spec_string ("e-tag",
						      _("E-Tag"),
						      _("Unique identifier for this content"),
						      NULL,
						      G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
                                   PROP_RESPONSE_LAST_MODIFIED,
                                   g_param_spec_long ("last-modified",
						      _("Last Modified"),
						      _("Time this content was last changed"),
						      -1, G_MAXLONG,
						      -1,
						      G_PARAM_READWRITE));
  g_object_class_install_property (object_class,
                                   PROP_RESPONSE_SERVER,
                                   g_param_spec_string ("server",
						      _("Server"),
						      _("Name of this webserver program"),
						      NULL,
						      G_PARAM_READWRITE));
}


GType gsk_http_response_get_type()
{
  static GType http_response_type = 0;
  if (!http_response_type)
    {
      static const GTypeInfo http_response_info =
      {
        sizeof(GskHttpResponseClass),
        (GBaseInitFunc) NULL,
        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc) gsk_http_response_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (GskHttpResponse),
        8,              /* n_preallocs */
        (GInstanceInitFunc) gsk_http_response_init,
        NULL            /* value_table */
      };
      http_response_type = g_type_register_static (GSK_TYPE_HTTP_HEADER,
                                                   "GskHttpResponse",
                                                   &http_response_info,
                                                   0);
    }
  return http_response_type;
}

/* --- public methods --- */
/**
 * gsk_http_response_set_cache_control:
 * @response: the HTTP response header to affect.
 * @directive: the new cache control directive, stolen by the HTTP header.
 * 
 * Set the cache-control flags in the header.
 * Note that @directive will be freed by the HTTP header when
 * it is destroyed.
 */
void
gsk_http_response_set_cache_control (GskHttpResponse *response,
				     GskHttpResponseCacheDirective *directive)
{
  if (response->cache_control)
    gsk_http_response_cache_directive_free (response->cache_control);
  response->cache_control = directive;
}

/**
 * gsk_http_response_set_md5:
 * @response: the HTTP response whose content MD5 sum is being given.
 * @md5sum: the 16-byte binary value of the MD5 hash of the content.
 * If this is NULL, unset the MD5 field.
 *
 * Set the MD5 field of the HTTP response to the given @md5sum.
 * GSK does not attempt to verify the MD5 sum;
 * the caller should be sure they have the right MD5 sum.
 */
void
gsk_http_response_set_md5      (GskHttpResponse *response,
			        const guint8    *md5sum)
{
  if (md5sum == NULL)
    {
      response->has_md5sum = 0;
    }
  else
    {
      memcpy (response->md5sum, md5sum, 16);
      response->has_md5sum = 1;
    }
}

/**
 * gsk_http_response_peek_md5:
 * @response: the response to query.
 * 
 * Get the MD5 sum associated with this response.
 * (It should be the MD5 sum of the content stream).
 *
 * It will return NULL if there is no associated content MD5 sum.
 * This is the default state.
 *
 * returns: the MD5 checksum (as 16 binary bytes) or NULL.
 */
const guint8 *
gsk_http_response_peek_md5     (GskHttpResponse *response)
{
  return response->has_md5sum ? response->md5sum : NULL;
}

/**
 * gsk_http_response_set_retry_after:
 * @response: the response to set a Retry-After line in.
 * @is_relative: whether the time should be relative to the current time (in which case it is
 * printed as a raw integer), or whether it is an absolute time (in which case it is printed
 * in the standard date format.)
 * @time: the time in seconds.  If is_relative, then its the number of seconds after
 * the current time; otherwise, it's unix time (ie seconds after Jan 1 1970, GMT).
 *
 * Set the Retry-After header for this response.
 * 
 * [From RFC 2616, Section 14.37]
 * The Retry-After response-header field can be used with a 503 (Service
 * Unavailable) response to indicate how long the service is expected to
 * be unavailable to the requesting client. This field MAY also be used
 * with any 3xx (Redirection) response to indicate the minimum time the
 * user-agent is asked wait before issuing the redirected request.
 */
void       gsk_http_response_set_retry_after   (GskHttpResponse *response,
                                                gboolean         is_relative,
                                                glong            time)
{
  response->has_retry_after = 1;
  response->retry_after_relative = is_relative;
  response->retry_after = time;
}

/**
 * gsk_http_response_set_no_retry_after:
 * @response: the response to clear the Retry-After line from.
 *
 * Clear the Retry-After header for this response.
 */
void       gsk_http_response_set_no_retry_after(GskHttpResponse *response)
{
  response->has_retry_after = 0;
}

/**
 * gsk_http_response_set_authenticate:
 * @response: the response to adjust the authentication for.
 * @is_proxy_auth: whether to set the Proxy-Authorization or the Authorization field.
 * @auth: the new authentication to use in this response.
 *
 * Set the authentication for this response.
 * This is like a key to get access to certain entities.
 *
 * Proxy-Authorization is intended to provide access control to the proxy.
 * Normal Authorization is passed through a proxy.
 * See sections 14.8 for normal Authorization and 14.34.
 */
void
gsk_http_response_set_authenticate       (GskHttpResponse  *response,
					  gboolean         is_proxy_auth,
					  GskHttpAuthenticate *auth)
{
  GskHttpAuthenticate **dst_auth = is_proxy_auth
                                 ? &response->proxy_authenticate
                                 : &response->authenticate;

  if (auth)
    gsk_http_authenticate_ref (auth);
  if (*dst_auth != NULL)
    gsk_http_authenticate_unref (*dst_auth);

  *dst_auth = auth;
}

/**
 * gsk_http_response_peek_authenticate:
 * @response: the response to query.
 * @is_proxy_auth: whether to query information about proxy authentication,
 * or normal server authentication.
 *
 * Get the responseed authentication information.
 *
 * returns: the authentication information, or NULL if none exists (default).
 */
GskHttpAuthenticate *
gsk_http_response_peek_authenticate      (GskHttpResponse  *response,
					  gboolean    is_proxy_auth)
{
  return is_proxy_auth ? response->proxy_authenticate : response->authenticate;
}

/**
 * gsk_http_response_set_allowed_verbs:
 * @response: the response to affect.
 * @allowed: bits telling which type of requests this server allows.
 * The bit for a verb 'Q' is (1 &lt;&lt; GSK_HTTP_VERB_Q);
 * the default allowed bits are (1&lt;&lt;GSK_HTTP_VERB_GET) | (1&lt;&lt;GSK_HTTP_VERB_HEAD).
 *
 * Set the Allow: header to indicate a particular set of HTTP verbs
 * are allowed from the client.  If you give a request with
 * a verb which is not allowed, you should get a GSK_HTTP_STATUS_METHOD_NOT_ALLOWED response.
 * That response MUST have an Allow: header.
 */
void       gsk_http_response_set_allowed_verbs  (GskHttpResponse *response,
                                                 guint            allowed)
{
  response->allowed_verbs = allowed;
}

/* --- standard header constructors --- */

/* Responses. */
/**
 * gsk_http_response_new_blank:
 * 
 * Create a new, empty default response.
 *
 * returns: the newly allocated response.
 */
GskHttpResponse *gsk_http_response_new_blank    (void)
{
  return g_object_new (GSK_TYPE_HTTP_RESPONSE, NULL);
}

/**
 * gsk_http_response_new_redirect:
 * @location: the URL to redirect the client to, as a string.
 *
 * Create a redirection HTTP response header.
 *
 * We use the 302 ("Found") status code.
 *
 * returns: the newly allocated header.
 */
GskHttpResponse *
gsk_http_response_new_redirect (const char    *location)
{
  GskHttpResponse *response;
  response = gsk_http_response_new_blank ();
  response->status_code = GSK_HTTP_STATUS_FOUND;	/* 302 */
  gsk_http_response_set_location (response, location);
  return response;
}

/* --- responding to a request (made easy) --- */
/**
 * gsk_http_response_from_request:
 * @request: the client's request to match.
 * @status_code: the return status of the request.
 * @length: the length of the message, or -1 for unknown.
 *
 * Create a response which macthes the request,
 * with a caller-supplied status code and optional length.
 *
 * returns: a new, matching response.
 */
GskHttpResponse  *
gsk_http_response_from_request (GskHttpRequest *request,
			        GskHttpStatus   status_code,
				gint64          length)
{
  GskHttpHeader *header_request = request ? GSK_HTTP_HEADER (request) : NULL;
  GskHttpResponse *response = gsk_http_response_new_blank ();
  GskHttpHeader *header_response = GSK_HTTP_HEADER (response);
  response->status_code = status_code;
  header_response->content_length = length;

  if (request == NULL)
    {
      gsk_http_header_set_version (header_response, 1, 0);
    }
  else
    {
      header_response->connection_type = header_request->connection_type;
      gsk_http_header_set_version (header_response,
				   header_request->http_major_version,
				   header_request->http_minor_version);
    }

  if (length < 0)
    {
      if (   request != NULL
          && header_request->http_minor_version >= 1
          && status_code == GSK_HTTP_STATUS_OK)
        header_response->transfer_encoding_type = GSK_HTTP_TRANSFER_ENCODING_CHUNKED;
      else
        header_response->connection_type = GSK_HTTP_CONNECTION_CLOSE;
    }

  return response;
}


syntax highlighted by Code2HTML, v. 0.9.1