/* 
   HTTP Request Handling
   Copyright (C) 1999-2001, Joe Orton <joe@light.plus.com>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library 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

*/

#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H

#include <stdio.h> /* For FILE * */

#include "http_utils.h" /* For http_status */
#include "string_utils.h" /* For sbuffer */
#include "nsocket.h" /* for nssl_context */

BEGIN_NEON_DECLS

#define HTTP_OK (0)
#define HTTP_ERROR (1) /* Generic error; use http_get_error(session) for message */
#define HTTP_LOOKUP (3) /* Name lookup failed */
#define HTTP_AUTH (4) /* User authentication failed on server */
#define HTTP_AUTHPROXY (5) /* User authentication failed on proxy */
#define HTTP_SERVERAUTH (6) /* Server authentication failed */
#define HTTP_PROXYAUTH (7) /* Proxy authentication failed */
#define HTTP_CONNECT (8) /* Could not connect to server */
#define HTTP_TIMEOUT (9) /* Connection timed out */
#define HTTP_FAILED (10) /* The precondition failed */
#define HTTP_RETRY (11) /* Retry request (http_end_request ONLY) */
#define HTTP_REDIRECT (12) /* See http_redirect.h */

#define EOL "\r\n"

/****** Session Handling ******/

typedef struct http_session_s http_session;
typedef struct http_req_s http_req;

/* 
 * Session handling:
 *  Call order:
 *  0.   sock_init             MANDATORY
 *  1.   http_session_create   MANDATORY
 *  2.   http_session_proxy    OPTIONAL
 *  3.   http_session_server   MANDATORY
 *  4.   http_set_*            OPTIONAL
 *  ...  --- Any HTTP request method ---
 *  n.   http_session_destroy  MANDATORY
 */
 
/* Create a new HTTP session */
http_session *http_session_create(void);

/* Finish an HTTP session */
int http_session_destroy(http_session *sess);

void http_session_ftp(http_session *sess, const char *userinfo);

/* Set the server or proxy server to be used for the session.
 * Returns:
 *   HTTP_LOOKUP if the DNS lookup for hostname failed.
 *   HTTP_OK otherwise.
 *
 * Note that if a proxy is being used, http_session_proxy should be
 * called BEFORE http_session_server, so the DNS lookup can be skipped
 * on the server. */
int http_session_server(http_session *sess, const char *hostname, int port);
int http_session_proxy(http_session *sess, const char *hostname, int port);

/* Callback to determine whether the proxy server should be used or
 * not for a request to the given hostname using the given scheme.
 * Scheme will be "http" or "https" etc.
 * Must return:
 *   Zero to indicate the proxy should NOT be used
 *   Non-zero to indicate the proxy SHOULD be used.
 */
typedef int (*http_use_proxy)(void *userdata,
			      const char *scheme, const char *hostname);

/* Register the callback for determining whether the proxy server
 * should be used or not here.  'userdata' will be passed as the first
 * argument to the callback. The callback is only called if a proxy
 * server has been set up using http_session_proxy. 
 *
 * This function MUST be called before calling http_session_server.
 */
void http_session_decide_proxy(http_session *sess, http_use_proxy use_proxy,
			       void *userdata);

/* Set protocol options for session:
 *   expect100: Defaults to OFF
 *   persist:   Defaults to ON
 *
 * expect100: When set, send the "Expect: 100-continue" request header
 * with requests with bodies.
 *
 * persist: When set, use a persistent connection. (Generally,
 * you don't want to turn this off.)
 * */
void http_set_expect100(http_session *sess, int use_expect100);
void http_set_persist(http_session *sess, int persist);

/* Set a progress callback for the session. */
void http_set_progress(http_session *sess, 
		       sock_progress progress, void *userdata);

typedef enum {
    http_conn_namelookup, /* lookup up hostname (info = hostname) */
    http_conn_connecting, /* connecting to host (info = hostname) */
    http_conn_connected, /* connected to host (info = hostname) */
    http_conn_secure /* connection now secure (info = crypto level) */
} http_conn_status;

typedef void (*http_notify_status)(void *userdata, 
				   http_conn_status status,
				   const char *info);

/* Set a status notification callback for the session, to report
 * connection status. */
void http_set_status(http_session *sess,
		     http_notify_status status, void *userdata);

/* Using SSL/TLS connections:
 *
 * Session settings:
 *   secure:                 Defaults to OFF
 *   secure_context:         No callbacks, any protocol allowed.
 *   request_secure_upgrade: Defaults to OFF
 *   accept_secure_ugprade:  Defaults to OFF
 *
 * secure_context: When set, specifies the settings to use for secure
 * connections, and any callbacks (see nsocket.h).  The lifetime of
 * the context object must be >= to the lifetime of the session
 * object.
 *
 * secure: When set, use a secure (SSL/TLS) connection to the origin
 * server. This will tunnel (using CONNECT) through the proxy server
 * if one is being used.
 *
 * request_secure_upgrade: When set, notify the server with each
 * request made that an upgrade to a secure connection is desired, if
 * available.
 *
 * accept_secure_upgrade: When set, allow a server-requested upgrade
 * to a secure connection.
 *
 * NB: the latter three function calls will return -1 if SSL is not
 * supported by the library (i.e., it was not built against OpenSSL),
 * or 0 if it is.
 * */
void http_set_secure_context(http_session *sess, nssl_context *ctx);
int http_set_secure(http_session *sess, int secure);
int http_set_request_secure_upgrade(http_session *sess, int req_upgrade);
int http_set_accept_secure_upgrade(http_session *sess, int acc_upgrade);

/* Sets the user-agent string. neon/VERSION will be appended, to make
 * the full header "User-Agent: product neon/VERSION".
 * If this function is not called, the User-Agent header is not sent.
 * The product string must follow the RFC2616 format, i.e.
 *       product         = token ["/" product-version]
 *       product-version = token
 * where token is any alpha-numeric-y string [a-zA-Z0-9]*
 */
void http_set_useragent(http_session *sess, const char *product);

/* Determine if next-hop server claims HTTP/1.1 compliance. Returns:
 *   0 if next-hop server does NOT claim HTTP/1.1 compliance
 *   non-zero if next-hop server DOES claim HTTP/1.1 compliance
 * Not that the "next-hop" server is the proxy server if one is being
 * used, otherwise, the origin server.
 */
int http_version_pre_http11(http_session *sess);

/* Returns the 'hostport' URI segment for the end-server, e.g.
 *    "my.server.com:8080"    or    "www.server.com" 
 *  (port segment is ommitted if == 80) 
 */
const char *http_get_server_hostport(http_session *sess);

/* Returns the URL scheme being used for the current session.
 * Does NOT include a trailing ':'. 
 * Example returns: "http" or "https".
 */
const char *http_get_scheme(http_session *sess);

/* Set the error string for the session */
void http_set_error(http_session *sess, const char *errstring);
/* Retrieve the error string for the session */
const char *http_get_error(http_session *sess);

/**** Request hooks handling *****/

typedef struct {
    /* A slight hack? Allows access to the hook private information,
     * externally... */
    const char *id; /* id could be a URI to be globally unique */
    /* e.g. "http://webdav.org/neon/hooks/davlock" */
    
    /* Register a new request: return value passed to subsequent
     * handlers as '_private'. Session cookie passed as 'session'. */
    void *(*create)(void *cookie, http_req *req, 
		    const char *method, const char *uri);
    /* Tell them what request body we are using: either buffer or stream
     * will be non-NULL. */
    void (*use_body)(void *_private, const char *buffer, FILE *stream);
    /* Just before sending the request: let them add headers if they want */
    void (*pre_send)(void *_private, sbuffer request);
    /* After sending the request. May return:
     *  HTTP_OK     everything is okay
     *  HTTP_RETRY  try sending the request again.
     *  otherwise: request fails.  Returned to _dispatch caller.
     */
    int (*post_send)(void *_private, const http_status *status);
    /* Clean up after yourself. */
    void (*destroy)(void *_private);
} http_request_hooks;

typedef void (*http_free_hooks)(void *cookie);

/* Add in hooks.
 *  'cookie' will be passed to each call to the 'create' handler of the hooks.
 * If 'free_hooks' is non-NULL, it will called when the session is destroyed,
 * to free any data associated with 'cookie'.
 */
void http_add_hooks(http_session *sess, 
		    const http_request_hooks *hooks, void *cookie,
		    http_free_hooks free_cookie);

/* Return private data for a new request */ 
void *http_request_hook_private(http_req *req, const char *id);

/* Return private data for the session */
void *http_session_hook_private(http_session *sess, const char *id);

/***** Request Handling *****/

/* Create a new request, with given method and URI. 
 * Returns request pointer. */
http_req *
http_request_create(http_session *sess, const char *method, const char *uri);

/* 'buffer' will be sent as the request body with given request. */
void http_set_request_body_buffer(http_req *req, const char *buffer,
				  size_t size);

/* Contents of stream will be sent as the request body with the given
 * request. Returns:
 *  0 on okay.
 *  non-zero if could not determine length of file.
 */
int http_set_request_body_stream(http_req *req, FILE *stream);

/* Callback for providing request body blocks.
 *
 * Before each time the body is provided, the callback will be called
 * once with buflen == 0.  The body may have to be provided >1 time
 * per request (for authentication retries etc.).
 *
 * Must return:
 *     0 < x <= buflen  => buffer contains x bytes of body data.
 * or  0 => ignore 'buffer' contents, end of body.
 */
typedef size_t (*http_provide_body)(void *userdata, 
				    char *buffer, size_t buflen);

/* Callback is called to provide blocks of request body. */
void http_set_request_body_provider(http_req *req, size_t size,
				   http_provide_body provider, void *userdata);

/* Handling response bodies... you provide TWO callbacks:
 *
 * 1) 'acceptance' callback: determines whether you want to handle the
 * response body given the response-status information, e.g., if you
 * only want 2xx responses, say so here.
 *
 * 2) 'reader' callback: passed blocks of the response-body as they
 * arrive, if the acceptance callback returned non-zero.  */

/* 'acceptance' callback type. Return non-zero to accept the response,
 * else zero to ignore it. */
typedef int (*http_accept_response)(
    void *userdata, http_req *req, http_status *st);

/* An 'acceptance' callback which only accepts 2xx-class responses.
 * Ignores userdata. */
int http_accept_2xx(void *userdata, http_req *req, http_status *st);

/* An acceptance callback which accepts all responses.  Ignores
 * userdata. */
int http_accept_always(void *userdata, http_req *req, http_status *st);

/* The 'reader' callback type */
typedef void (*http_block_reader)(
    void *userdata, const char *buf, size_t len);

/* Add a response reader for the given request, with the given
 * acceptance function. userdata is passed as the first argument to
 * the acceptance + reader callbacks. */
void http_add_response_body_reader(http_req *req, http_accept_response accpt,
				   http_block_reader rdr, void *userdata);

/* Handle response headers. Each handler is associated with a specific
 * header field (indicated by name). The handler is then passed the
 * value of this header field. */

/* The header handler callback type */
typedef void (*http_header_handler)(void *userdata, const char *value);

/* Adds a response header handler for the given request. userdata is passed
 * as the first argument to the header handler, and the 'value' is the
 * header field value (i.e. doesn't include the "Header-Name: " part").
 */
void http_add_response_header_handler(http_req *req, const char *name, 
				      http_header_handler hdl, void *userdata);

/* Add handler which is passed ALL header values regardless of name */
void http_add_response_header_catcher(http_req *req, 
				      http_header_handler hdl, void *userdata);

/* Stock header handlers:
 *  'duplicate': *(char **)userdata = strdup(value)
 *  'numeric':   *(int *)userdata = atoi(value)
 * e.g.
 *   int mynum;
 *   http_add_response_header_handler(myreq, "Content-Length",
 *                                    http_handle_numeric_handler, &mynum);
 * ... arranges mynum to be set to the value of the Content-Length header.
 */
void http_duplicate_header(void *userdata, const char *value);
void http_handle_numeric_header(void *userdata, const char *value);

/* Adds a header to the request with given name and value. */
void http_add_request_header(http_req *req, const char *name, 
			     const char *value);
/* Adds a header to the request with given name, using printf-like
 * format arguments for the value. */
void http_print_request_header(http_req *req, const char *name,
			       const char *format, ...)
#ifdef __GNUC__
                __attribute__ ((format (printf, 3, 4)))
#endif /* __GNUC__ */
;

sbuffer http_get_request_headers(http_req *req);

/* http_request_dispatch: Sends the given request, and reads the
 * response. Response-Status information can be retrieve with
 * http_get_status(req).
 *
 * Returns:
 *  HTTP_OK         if request sent + response read okay.
 *  HTTP_AUTH       if user authentication failed on origin server
 *  HTTP_AUTHPROXY  if user authentication failed on proxy server
 *  HTTP_SERVERAUTH server authentication failed
 *  HTTP_PROXYAUTH  proxy authentication failed
 *  HTTP_CONNECT    could not connect to server/proxy server
 *  HTTP_TIMEOUT    connection timed out mid-request
 *  HTTP_ERROR      for other errors, and http_get_error() should
 *                  return a meaningful error string
 *
 * NB: HTTP_AUTH and HTTP_AUTHPROXY mean that the USER supplied the
 * wrong username/password.  SERVER/PROXYAUTH mean that, after the
 * server has accepted a valid username/password, the server/proxy did
 * not authenticate the response message correctly.
 * */
int http_request_dispatch(http_req *req);

/* Returns a pointer to the response status information for the
 * given request. */
const http_status *http_get_status(http_req *req)

/* Declare this with attribute const, since we often call it >1 times
 * with the same argument, and it will return the same thing each
 * time. This lets the compiler optimize away any subsequent calls
 * (theoretically).  */
#ifdef __GNUC__
                __attribute__ ((const))
#endif /* __GNUC__ */
    ;

/* Destroy memory associated with request pointer */
void http_request_destroy(http_req *req);

/* "Caller-pulls" request interface.  This is an ALTERNATIVE interface
 * to http_request_dispatch: either use that, or do all this yourself:
 *
 * caller must call:
 *  1. http_begin_request (fail if returns non-HTTP_OK)
 *  2. while(http_read_response_block(...) > 0) ... loop ...;
 *  3. http_end_request
 * If http_end_request returns HTTP_RETRY, MUST go back to 1.
 */
int http_begin_request(http_req *req);
int http_end_request(http_req *req);

/* Read a block of the response.  buffer must be at least 128 bytes.
 * 'buflen' must be length of buffer.
 *
 * Returns:
 *  <0 - error, stop reading.
 *   0 - end of response
 *  >0 - number of bytes read into buffer.
 */
ssize_t http_read_response_block(http_req *req, char *buffer, size_t buflen);

END_NEON_DECLS

#endif /* HTTP_REQUEST_H */

