/* 
   HTTP Request Handling
   Copyright (C) 1999-2024, Joe Orton <joe@manyfish.co.uk>

   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 NE_REQUEST_H
#define NE_REQUEST_H

#include "ne_utils.h" /* For ne_status */
#include "ne_string.h" /* For ne_buffer */
#include "ne_session.h"

NE_BEGIN_DECLS

#define NE_OK (0) /* Success */
#define NE_ERROR (1) /* Generic error; use ne_get_error(session) for message */
#define NE_LOOKUP (2) /* Server or proxy hostname lookup failed */
#define NE_AUTH (3) /* User authentication failed on server */
#define NE_PROXYAUTH (4) /* User authentication failed on proxy */
#define NE_CONNECT (5) /* Could not connect to server */
#define NE_TIMEOUT (6) /* Connection timed out */
#define NE_FAILED (7) /* The precondition failed */
#define NE_RETRY (8) /* Retry request (ne_end_request ONLY) */
#define NE_REDIRECT (9) /* See ne_redirect.h */

/* Opaque object representing a single HTTP request. */
typedef struct ne_request_s ne_request;

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

/* Create a request in session 'sess', with given method and target.
 * 'target' is used to form the request-target (per RFC 9112ẞ3.2), and
 * may be an absolute-path (with optional query-string), an
 * absolute-URI, or an asterisk. */
ne_request *ne_request_create(ne_session *sess, const char *method,
                              const char *target)
    ne_attribute((nonnull));

/* The request body will be taken from 'size' bytes of 'buffer'. */
void ne_set_request_body_buffer(ne_request *req, const char *buffer,
                                size_t size)
    ne_attribute((nonnull));

/* The request body will be taken from 'length' bytes read from the
 * file descriptor 'fd', starting from file offset 'offset'. */
void ne_set_request_body_fd(ne_request *req, int fd,
                            ne_off_t offset, ne_off_t length)
    ne_attribute((nonnull));

/* "Pull"-based request body provider: a callback which is invoked to
 * provide blocks of request body on demand.
 *
 * 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.).
 *
 * For a call with buflen == 0, the callback must return zero on success
 * or non-zero on error; the session error string must be set on error.
 * For a call with buflen > 0, the callback must return:
 *        <0           : error, abort request; session error string must be set.
 *         0           : ignore 'buffer' contents, end of body.
 *     0 < x <= buflen : buffer contains x bytes of body data.  */
typedef ssize_t (*ne_provide_body)(void *userdata, char *buffer, size_t buflen);

/* Install a callback which is invoked as needed to provide the
 * request body, a block at a time.  The total size of the request
 * body is 'length'; the callback must ensure that it returns no more
 * than 'length' bytes in total.  If 'length' is set to -1, then the
 * total size of the request is unknown by the caller and chunked
 * transfer will be used.
 *
 * The caller MUST determine that the server can accept chunked
 * encoding (i.e. advertises HTTP/1.1 support) before using chunked
 * encoding. This can be done by testing that ne_version_pre_http11()
 * returns zero after performing an OPTIONS or HEAD request. */
void ne_set_request_body_provider(ne_request *req, ne_off_t length,
                                  ne_provide_body provider, void *userdata)
    ne_attribute((nonnull (1, 3)));

/* Handling response bodies; two callbacks must be provided:
 *
 * 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 (*ne_accept_response)(void *userdata, ne_request *req, 
                                  const ne_status *st);

/* An 'acceptance' callback which only accepts 2xx-class responses.
 * Ignores userdata. */
int ne_accept_2xx(void *userdata, ne_request *req, const ne_status *st);

/* An acceptance callback which accepts all responses.  Ignores
 * userdata. */
int ne_accept_always(void *userdata, ne_request *req, const ne_status *st);

/* Callback for reading a block of data.  Returns zero on success, or
 * non-zero on error.  If returning an error, the response will be
 * aborted and the callback will not be invoked again.  The request
 * dispatch (or ne_read_response_block call) will fail with NE_ERROR;
 * the session error string should have been set by the callback. */
typedef int (*ne_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. 
 *
 * The acceptance callback is called once each time the request is
 * sent: it may be sent >1 time because of authentication retries etc.
 * For each time the acceptance callback is called, if it returns
 * non-zero, blocks of the response body will be passed to the reader
 * callback as the response is read.  After all the response body has
 * been read, the callback will be called with a 'len' argument of
 * zero.  */
void ne_add_response_body_reader(ne_request *req, ne_accept_response accpt,
				 ne_block_reader reader, void *userdata)
    ne_attribute((nonnull (1, 2, 3)));

/* Retrieve the value of the response header field with given name;
 * returns NULL if no response header with given name was found.  The
 * return value is valid only until the next call to either
 * ne_request_destroy or ne_begin_request for this request. */
const char *ne_get_response_header(ne_request *req, const char *name)
    ne_attribute((nonnull));

/* Iterator interface for response headers: if passed a NULL cursor,
 * returns the first header; if passed a non-NULL cursor pointer,
 * returns the next header.  The return value is a cursor pointer: if
 * it is non-NULL, *name and *value are set to the name and value of
 * the header field.  If the return value is NULL, no more headers are
 * found, *name and *value are undefined.
 *
 * The order in which response headers is returned is undefined.  Both
 * the cursor and name/value pointers are valid only until the next
 * call to either ne_request_destroy or ne_begin_request for this
 * request. */
void *ne_response_header_iterate(ne_request *req, void *cursor,
                                 const char **name, const char **value);

/* Adds a header to the request with given name and value. */
void ne_add_request_header(ne_request *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 ne_print_request_header(ne_request *req, const char *name,
			     const char *format, ...) 
    ne_attribute((format(printf, 3, 4)));

/* Returns the request target URI as a malloc-allocated ne_uri object,
 * or NULL on error if the request target cannot be determined. The
 * session error string is not changed on error. */
const ne_uri *ne_get_request_target(ne_request *req)
    ne_attribute((nonnull));

/* If the response includes a Location header, this function parses
 * and resolves the URI-reference relative to the request target.  If
 * a fragment ("#fragment") is used for the request target, it can be
 * passed as an argument to allow relative resolution. Returns a
 * malloc-allocated ne_uri object, or NULL if the URI in the Location
 * header could not be parsed, or the Location header was not
 * present. */
ne_uri *ne_get_response_location(ne_request *req, const char *fragment)
    ne_attribute((nonnull (1)));

/* ne_request_dispatch: Sends the given request, and reads the
 * response.  Returns:
 *  - NE_OK if the request was sent and response read successfully
 *  - NE_AUTH, NE_PROXYAUTH for a server or proxy server authentication error
 *  - NE_CONNECT if connection could not be established
 *  - NE_TIMEOUT if an timeout occurred sending or reading from the server
 *  - NE_ERROR for other fatal dispatch errors
 * On any error, the session error string is set.  On success or
 * authentication error, the actual response-status can be retrieved using
 * ne_get_status(). */
int ne_request_dispatch(ne_request *req) ne_attribute((nonnull (1)));

/* Returns a pointer to the response status information for the given
 * request; pointer is valid until request object is destroyed. */
const ne_status *ne_get_status(const ne_request *req) ne_attribute((const));

/* Returns pointer to session associated with request. */
ne_session *ne_get_session(const ne_request *req) ne_attribute((const));

/* Destroy memory associated with request pointer */
void ne_request_destroy(ne_request *req);

/* "Caller-pulls" request interface.  This is an ALTERNATIVE interface
 * to ne_request_dispatch: either use that, or do all this yourself:
 *
 * caller must call:
 *  1. ne_begin_request (fail if returns non-NE_OK)
 *  2. while(ne_read_response_block(...) > 0) ... loop ...;
 *     (fail if ne_read_response_block returns <0)
 *  3. ne_end_request
 *
 * ne_end_request and ne_begin_request both return an NE_* code; if
 * ne_end_request returns NE_RETRY, you must restart the loop from (1)
 * above. */
int ne_begin_request(ne_request *req);
int ne_end_request(ne_request *req);

/* Read a block of the response into the passed buffer of size 'buflen'.
 *
 * Returns:
 *  <0 - error, stop reading.
 *   0 - end of response
 *  >0 - number of bytes read into buffer.
 */
ssize_t ne_read_response_block(ne_request *req, char *buffer, size_t buflen);

/* Read response blocks until end of response; exactly equivalent to
 * calling ne_read_response_block() until it returns 0.  Returns
 * non-zero on error. */
int ne_discard_response(ne_request *req);

/* Read response blocks until end of response, writing content to the
 * given file descriptor.  Returns NE_ERROR on error. */
int ne_read_response_to_fd(ne_request *req, int fd);

/* Defined request flags: */
typedef enum ne_request_flag_e {
    NE_REQFLAG_EXPECT100 = 0, /* enable this flag to enable use of the
                               * "Expect: 100-continue" for the
                               * request. */

    NE_REQFLAG_IDEMPOTENT, /* disable this flag if the request uses a
                            * non-idempotent method such as POST. */

    NE_REQFLAG_1XXTIMEOUT, /* disable this flag to apply no overall
                             * timeout when reading interim
                             * responses. */

    NE_REQFLAG_LAST /* enum sentinel value */
} ne_request_flag;

/* Set a new value for a particular request flag. */
void ne_set_request_flag(ne_request *req, ne_request_flag flag, int value);

/* Return 0 if the given flag is not set, >0 it is set, or -1 if the
 * flag is not supported. */
int ne_get_request_flag(ne_request *req, ne_request_flag flag);

/* Callback to handle an interim (1xx) response. The status-code of
 * the response is passed as 'status'; interim response headers can be
 * accessed via ne_get_response_header. */
typedef void (*ne_interim_response_fn)(void *userdata, ne_request *req,
                                       const ne_status *status);

/* Add a interim response callback handler for the request. */
void ne_add_interim_handler(ne_request *req, ne_interim_response_fn fn,
                            void *userdata);

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

/* Hook called when a request is created; passed the method and
 * request-target as used in the request-line (RFC 9112§3.2).  The
 * create_request hook is called exactly once per request. */
typedef void (*ne_create_request_fn)(ne_request *req, void *userdata,
				     const char *method, const char *target);
void ne_hook_create_request(ne_session *sess, 
			    ne_create_request_fn fn, void *userdata);

/* Hook called before the request is sent.  'header' is the raw HTTP
 * header before the trailing CRLF is added; more headers can be added
 * here.  A pre_send hook may be called >1 time per request if the
 * request is retried due to a post_send hook returning NE_RETRY. */
typedef void (*ne_pre_send_fn)(ne_request *req, void *userdata, 
			       ne_buffer *header);
void ne_hook_pre_send(ne_session *sess, ne_pre_send_fn fn, void *userdata);

/* Hook called directly after the response headers have been read, but
 * before the resposnse body has been read.  'status' is the response
 * status-code.  A post_header hook may be called >1 time per request
 * if the request is retried due to a post_send hook returning
 * NE_RETRY. */
typedef void (*ne_post_headers_fn)(ne_request *req, void *userdata,
                                   const ne_status *status);
void ne_hook_post_headers(ne_session *sess, 
                          ne_post_headers_fn fn, void *userdata);

/* Hook called after the request is dispatched (request sent, and
 * the entire response read).  If an error occurred reading the response,
 * this hook will not run.  May return:
 *  NE_OK     everything is okay
 *  NE_RETRY  try sending the request again.
 * anything else signifies an error, and the request is failed. The return
 * code is passed back the _dispatch caller, so the session error must
 * also be set appropriately (ne_set_error).
 */
typedef int (*ne_post_send_fn)(ne_request *req, void *userdata,
			       const ne_status *status);
void ne_hook_post_send(ne_session *sess, ne_post_send_fn fn, void *userdata);

/* Hook called when the function is destroyed. */
typedef void (*ne_destroy_req_fn)(ne_request *req, void *userdata);
void ne_hook_destroy_request(ne_session *sess,
			     ne_destroy_req_fn fn, void *userdata);

typedef void (*ne_destroy_sess_fn)(void *userdata);
/* Hook called when the session is about to be destroyed. */
void ne_hook_destroy_session(ne_session *sess,
			     ne_destroy_sess_fn fn, void *userdata);

typedef void (*ne_close_conn_fn)(void *userdata);
/* Hook called when the connection is closed; note that this hook
 * may be called *AFTER* the destroy_session hook. */
void ne_hook_close_conn(ne_session *sess, ne_close_conn_fn fn, void *userdata);

/* The ne_unhook_* functions remove a hook registered with the given
 * session.  If a hook is found which was registered with a given
 * function 'fn', and userdata pointer 'userdata', then it will be
 * removed from the hooks list.
 *
 * It is unsafe to use any of these functions from a hook function to
 * unregister itself, except for ne_unhook_destroy_request. */
void ne_unhook_create_request(ne_session *sess, 
                              ne_create_request_fn fn, void *userdata);
void ne_unhook_pre_send(ne_session *sess, ne_pre_send_fn fn, void *userdata);
void ne_unhook_post_headers(ne_session *sess, ne_post_headers_fn fn, void *userdata);
void ne_unhook_post_send(ne_session *sess, ne_post_send_fn fn, void *userdata);
void ne_unhook_destroy_request(ne_session *sess,
                               ne_destroy_req_fn fn, void *userdata);
void ne_unhook_destroy_session(ne_session *sess,
                               ne_destroy_sess_fn fn, void *userdata);
void ne_unhook_close_conn(ne_session *sess, 
                          ne_close_conn_fn fn, void *userdata);

/* Store an opaque context for the request, 'priv' is returned by a
 * call to ne_request_get_private with the same ID. */
void ne_set_request_private(ne_request *req, const char *id, void *priv);
void *ne_get_request_private(ne_request *req, const char *id);

NE_END_DECLS

#endif /* NE_REQUEST_H */
