#include "gskhttpheader.h" #include "gskhttprequest.h" #include "gskhttpresponse.h" #include "../gskmacros.h" #include #include #include #include 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 << GSK_HTTP_VERB_Q); * the default allowed bits are (1<<GSK_HTTP_VERB_GET) | (1<<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; }