/*====================================================================
* Copyright (c) 1995-2001 The Apache Group. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* 4. The names "Apache Server" and "Apache Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission.
*
* 5. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Group and was originally based
* on public domain software written at the National Center for
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
* For more information on the Apache Group and the Apache HTTP server
* project, please see .
*/
static char const *rcsid = "$Id: mod_http_ext.c,v 1.1 2001/10/23 02:03:18 eric Exp $";
/*
* mod_http_ext: accounting for HTTP Extensions [1]
*
* Eric Prud'hommeaux
*
* Jul 28 2001: started
*
* [1] http://www.w3.org/Protocols/HTTP/ietf-http-ext/
*/
#include "httpd.h"
#include "http_config.h"
#include "http_log.h"
#include "apr_hash.h"
#include "apr_strings.h" /* apr_pstr* */
#include "apr_lib.h" /* string parsing */
#include "http_protocol.h" /* for registering hooks */
#include "http_ext.h"
#include
/* INTERNAL CONSTANTS */
#define REQ_CONTEXT_KEY HTTP_EXT_URI"#mod_http_ext/request-context"
#define NS_LEN 2 /* Use APR_HASH_KEY_STRING if ns becomes variable length. */
/* ENABLE_DEBUG_LOGGING - compile in a small amount of extra code to handle
* the logALot directive and, surprisingly, log a lot when it's set.
*
* 1 - add the code
* 0 - don't add the code
*/
#define ENABLE_DEBUG_LOGGING 0
#if ENABLE_DEBUG_LOGGING
static int _logReturn (int what, request_rec *r, const char * const where, const char * const why);
#define LOGRETURN(WHAT, REQ, WHERE, WHY) return _logReturn(WHAT, REQ, WHERE, WHY)
#else
#define LOGRETURN(WHAT, REQ, WHERE, WHY) return (WHAT)
#endif
/* The request context goes into r->pool's user data. */
typedef struct httpExt_reqContext_struct {
apr_hash_t *instancesIn;
apr_hash_t *instancesOut;
int nextNsOut;
} httpExt_reqContext_t;
typedef struct {
/* dir useful in debugging */
char *dir;
} httpExt_directory_rec;
typedef struct {
#if ENABLE_DEBUG_LOGGING
int logALot;
#endif
} httpExt_server_rec;
typedef enum {URI, FIELD_NAME} declType_t;
typedef struct {
declType_t declType;
httpExt_notify_t * notify;
httpExt_handler_contract contract;
} extension_t;
typedef struct {
char *declName;
declType_t declType;
extension_t * extension;
char *ns;
httpExt_handler_contract contract;
apr_table_t * headers;
apr_table_t * declExts;
int mandatory;
int connection;
} instance_t;
static apr_hash_t * Extensions;
static void *_httpExt_ap_dir_config(apr_pool_t *p, char *d);
static void *_httpExt_ap_dir_merge(apr_pool_t *p, void *basev, void *overridesv);
static void *_httpExt_ap_server_config(apr_pool_t *p, server_rec *s);
static void *_httpExt_ap_server_merge(apr_pool_t *p, void *basev, void *overridesv);
static void _httpExt_ap_register_hooks (apr_pool_t *p);
static void _httpExt_ap_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s);
static int _httpExt_ap_post_read_request(request_rec *r);
static const command_rec _httpExt_ap_cmds[];
module AP_MODULE_DECLARE_DATA http_ext_module = {
STANDARD20_MODULE_STUFF,
_httpExt_ap_dir_config, /* create dir config */
_httpExt_ap_dir_merge, /* merge dir config */
_httpExt_ap_server_config, /* create server config */
_httpExt_ap_server_merge, /* merge server config */
_httpExt_ap_cmds, /* command table */
_httpExt_ap_register_hooks /* register hooks */
};
/* COMMAND HANDLERS */
static const command_rec _httpExt_ap_cmds[] = {
#if ENABLE_DEBUG_LOGGING
AP_INIT_FLAG("httpExt_logALot", cmd_server_set_flag_slot,
(void *) XtOffsetOf(httpExt_server_rec, logALot),
RSRC_CONF, "dump copious status to the error log"),
#endif
{ NULL }
};
/* SUPPORT FUNCTIONS */
#if ENABLE_DEBUG_LOGGING
/* _logReturn - log messages and return first parameter.
*/
static int _logReturn (int what, request_rec *r, const char * const where, const char * const why)
{
httpExt_server_rec *srvRec =
(httpExt_server_rec *)ap_get_module_config(r->server->module_config,
&http_ext_module);
if (srvRec->logALot > 0) {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 999, r->server,
"%s returning %d (%s) on %s %s", where,
what, why, r->connection->remote_ip, r->the_request);
}
return what;
}
#endif
/* _httpExt_getReqContext - get request context from request
*/
static httpExt_reqContext_t * _httpExt_getReqContext (request_rec * r)
{
httpExt_reqContext_t *ctx;
apr_pool_userdata_get((void **)&ctx, REQ_CONTEXT_KEY, r->pool);
assert(ctx != NULL);
return ctx;
}
/* _httpExt_genExtensionHeaders - generate HTTP extension headers
*/
static apr_status_t _httpExt_genExtensionHeaders (ap_filter_t *f, apr_bucket_brigade *b)
{
request_rec *r = f->r;
httpExt_reqContext_t *ctx = _httpExt_getReqContext(r);
const char * const names[2][2] = {{"Opt", "Man"}, {"C-Opt", "C-Man"}};
char *out[2][2] = {{NULL, NULL}, {NULL, NULL}};
apr_hash_index_t *hi;
/* Generate header values for {,C-}{Man,Opt}. */
for (hi = apr_hash_first(ctx->instancesOut); hi; hi=apr_hash_next(hi)) {
instance_t * instance;
char * name;
apr_hash_this(hi, (const void **)&name, NULL, (void **)&instance);
if (out[instance->connection][instance->mandatory] == NULL)
out[instance->connection][instance->mandatory] = apr_pstrcat(r->pool, "\"", name, "\"; ns=", instance->ns, NULL);
else
out[instance->connection][instance->mandatory] = apr_pstrcat(r->pool, out[instance->connection][instance->mandatory], "\"", name, "\"; ns=", instance->ns, NULL);
}
if (out[0][0] != NULL) apr_table_setn(r->headers_out, names[0][0], out[0][0]);
if (out[0][1] != NULL) apr_table_setn(r->headers_out, names[0][1], out[0][1]);
if (out[1][0] != NULL) apr_table_setn(r->headers_out, names[1][0], out[1][0]);
if (out[1][1] != NULL) apr_table_setn(r->headers_out, names[1][1], out[1][1]);
return ap_pass_brigade(f->next, b);
}
/* _httpExt_parseDecl - parse comma-delimited piece of HTTP extension header.
* Grammer taken from RFC 2774[1]:
*
* mandatory = "Man" ":" 1#ext-decl
* optional = "Opt" ":" 1#ext-decl
* c-mandatory = "C-Man" ":" 1#ext-decl
* c-optional = "C-Opt" ":" 1#ext-decl
*
* ext-decl = <"> ( absoluteURI | field-name ) <"> [ namespace ] [ decl-extensions ]
*
* namespace = ";" "ns" "=" header-prefix
* header-prefix = 2*DIGIT
*
* decl-extensions = *( decl-ext )
* decl-ext = ";" token [ "=" ( token | quoted-string ) ]
*
* [1] ftp://ftp.isi.edu/in-notes/rfc2774.txt
*/
static const char *_httpExt_parseDecl (request_rec *r, instance_t *result, const char *headerStr, int * pDisposition)
{
apr_pool_t * p = r->pool;
const char * declName;
/* Extract declName from between the quotes. */
if (*headerStr != '"') {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server,
"http_ext: could not parse header segment \"%s\"", headerStr);
*pDisposition = HTTP_INTERNAL_SERVER_ERROR;
return NULL;
}
declName = ++headerStr;
while (*headerStr && *headerStr != '"') ++headerStr;
if (*headerStr != '"') {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server,
"http_ext: could not parse header segment \"%s\"", headerStr);
*pDisposition = HTTP_INTERNAL_SERVER_ERROR;
return NULL;
}
*headerStr++ = 0;
result->ns = NULL;
if (strchr(declName, ':')) {
result->declName = declName;
result->declType = URI;
} else {
result->declName = apr_pstrdup(p, declName);
ap_str_tolower(result->declName);
result->declType = FIELD_NAME;
}
while (*headerStr == ';') {
/* Parameters ... */
char *parm;
char *cp;
char *end;
++headerStr;
parm = ap_get_token(p, &headerStr, 1);
/* Look for 'var = value' --- and make sure the var is in lcase. */
for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp) {
*cp = apr_tolower(*cp);
}
*cp++ = '\0'; /* Delimit var */
while (*cp && (apr_isspace(*cp) || *cp == '=')) {
++cp;
}
if (*cp == '"') {
++cp;
for (end = cp;
(*end && *end != '\n' && *end != '\r' && *end != '\"');
end++);
}
else {
for (end = cp; (*end && !apr_isspace(*end)); end++);
}
if (*end) {
*end = '\0'; /* strip ending quote or return */
}
if (result->ns == NULL) {
if (parm[0] != 'n' || parm[1] != 's' || parm[2] != 0) {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server,
"http_ext: could not parse header segment \"%s\"", headerStr);
*pDisposition = HTTP_INTERNAL_SERVER_ERROR;
return NULL;
}
result->ns = cp;
} else {
if (result->declExts == NULL)
result->declExts = apr_table_make(p, 3);
apr_table_setn(result->declExts, parm, cp);
}
}
if (*headerStr == ',') {
++headerStr;
}
return headerStr;
}
/* _httpExt_parseExtHeader - parse HTTP extension header through
* iterative calles to _httpExt_parseDecl
*/
static int _httpExt_parseExtHeader (apr_hash_t * byNs, apr_hash_t * byDeclName, request_rec *r, const char * const headerName, int mandatory, int connection)
{
const char * headerStr = apr_table_get(r->headers_in, headerName);
int ret = OK;
if (headerStr == NULL)
return OK;
while (*headerStr) {
instance_t *instance = (instance_t *) apr_pcalloc(r->pool, sizeof(instance_t));
headerStr = _httpExt_parseDecl(r, instance, headerStr, &ret);
if (apr_hash_get(byNs, instance->ns, NS_LEN)) {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server,
"http_ext: \"%s\" namespace overloaded", instance->ns);
return HTTP_INTERNAL_SERVER_ERROR;
}
instance->mandatory = mandatory;
instance->connection = connection;
instance->headers = apr_table_make(r->pool, 3);
apr_hash_set(byNs, instance->ns, NS_LEN, instance);
apr_hash_set(byDeclName, instance->declName, APR_HASH_KEY_STRING, instance);
}
return ret;
}
/* APACHE API FUNCTIONS */
static void _httpExt_ap_register_hooks (apr_pool_t *p)
{
ap_hook_post_config(_httpExt_ap_post_config,NULL,NULL,APR_HOOK_FIRST);
ap_hook_post_read_request(_httpExt_ap_post_read_request,NULL,NULL,APR_HOOK_MIDDLE);
ap_register_output_filter("httpExt_genExtensionHeaders", _httpExt_genExtensionHeaders, AP_FTYPE_CONTENT);
}
/* server and directory configurations */
static void *_httpExt_ap_dir_config (apr_pool_t *p, char *d)
{
httpExt_directory_rec *dirRec =
(httpExt_directory_rec *) apr_pcalloc (p, sizeof(httpExt_directory_rec));
/* debugging */
dirRec->dir = apr_pstrdup(p, d);
return dirRec;
}
static void *_httpExt_ap_dir_merge (apr_pool_t *p, void *basev, void *overridesv)
{
httpExt_directory_rec *merged, *base, *overrides;
merged = (httpExt_directory_rec *)apr_pcalloc(p, sizeof(httpExt_directory_rec));
base = (httpExt_directory_rec *)basev;
overrides = (httpExt_directory_rec *)overridesv;
merged->dir = apr_pstrcat(p, "(", base->dir == NULL ? "" : base->dir, ") -> (", overrides->dir, ")", NULL);
return (void *)merged;
}
static void *_httpExt_ap_server_config (apr_pool_t *p, server_rec *s)
{
httpExt_server_rec *srvRec = (httpExt_server_rec *)apr_pcalloc(p, sizeof(httpExt_server_rec));
#if ENABLE_DEBUG_LOGGING
srvRec->logALot = -1;
#endif
return srvRec;
}
static void *_httpExt_ap_server_merge (apr_pool_t *p, void *basev, void *overridesv)
{
httpExt_server_rec *merged, *base, *overrides;
merged = (httpExt_server_rec *)apr_pcalloc(p, sizeof(httpExt_server_rec));
base = (httpExt_server_rec *)basev;
overrides = (httpExt_server_rec *)overridesv;
#if ENABLE_DEBUG_LOGGING
merged->logALot = overrides->logALot == -1 ? base->logALot : overrides->logALot;
#endif
return merged;
}
/* _httpExt_ap_post_config - set up index for HTTP extensions.
*/
static void _httpExt_ap_post_config (apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
{
Extensions = apr_hash_make(p);
}
/* _httpExt_ap_post_read_request - parse, index and dispatch incoming HTTP
* extension headers. Calls httpExt_required_error when mandatory extensions
* are not registered.
*/
static int _httpExt_ap_post_read_request (request_rec *r)
{
apr_hash_t * byDeclName = apr_hash_make(r->pool);
apr_hash_t * byNs = apr_hash_make(r->pool);
httpExt_reqContext_t *ctx;
int ret;
ctx = (httpExt_reqContext_t *)apr_pcalloc(r->pool, sizeof(httpExt_reqContext_t));
if (apr_pool_userdata_set(ctx, REQ_CONTEXT_KEY, apr_pool_cleanup_null, r->pool) != APR_SUCCESS)
return HTTP_INTERNAL_SERVER_ERROR;
if ((r->main || r->prev) && 0)
/* it's an internal request */
return DECLINED;
ap_add_output_filter("httpExt_genExtensionHeaders", NULL, r, r->connection);
ret = _httpExt_parseExtHeader(byNs, byDeclName, r, "Man", 1, 0);
if (ret != OK)
return ret;
ret = _httpExt_parseExtHeader(byNs, byDeclName, r, "Opt", 0, 0);
if (ret != OK)
return ret;
ret = _httpExt_parseExtHeader(byNs, byDeclName, r, "C-Man", 1, 1);
if (ret != OK)
return ret;
ret = _httpExt_parseExtHeader(byNs, byDeclName, r, "C-Opt", 0, 1);
if (ret != OK)
return ret;
/* Make one pass through the headers looking for matching extensions.
* This is where we use the extensions indexed by . */
{
apr_array_header_t * hdrs_arr = apr_table_elts(r->headers_in);
apr_table_entry_t * hdrs = (apr_table_entry_t *) hdrs_arr->elts;
int i;
for (i = 0; i < hdrs_arr->nelts; ++i) {
char * prefix = apr_pstrdup(r->pool, hdrs[i].key);
char * value, * key;
instance_t * instance = NULL;
if (prefix == NULL || !isdigit(prefix[0]) ||
!isdigit(prefix[1]) || prefix[2] != '-')
continue;
key = prefix + 3;
prefix[2] = 0;
instance = (instance_t *)apr_hash_get(byNs, prefix, NS_LEN);
if (instance == NULL) {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server,
"http_ext: \"%s\" namespace not found", prefix);
return HTTP_INTERNAL_SERVER_ERROR;
}
value = apr_pstrdup(r->pool, hdrs[i].val);
apr_table_setn(instance->headers, key, value);
}
}
ctx->instancesIn = byDeclName;
ctx->nextNsOut = 0;
ctx->instancesOut = apr_hash_make(r->pool);
/* Now that we have the headers, call notify for each instance. */
{
apr_hash_index_t *hi;
/* Walk list of instances. */
for (hi = apr_hash_first(byNs); hi; hi=apr_hash_next(hi)) {
instance_t * instance;
extension_t * extension;
apr_hash_this(hi, NULL, NULL, (void **)&instance);
extension = (extension_t *)apr_hash_get(Extensions, instance->declName, APR_HASH_KEY_STRING);
if (extension == NULL) {
if (instance->mandatory)
/* No handler for mandatory extension so abort. */
return httpExt_required_error(r, instance->declName, instance->ns);
} else if (extension->notify) {
/* Call extension's notify */
instance->contract = (*extension->notify)(r, instance->declName, instance->headers, instance->declExts);
}
}
}
return DECLINED;
}
/* HTTP EXTENSIONS API FUNCTIONS */
/* httpExt_register_extension - register an HTTP extension module. This is
* called at post config time:
*
* static void myExt_register_hooks (apr_pool_t *p)
* {
* static const char * const listOfOne[]={ "mod_http_ext.c", NULL };
*
* ap_hook_post_config(myExt_post_config,
* listOfOne, NULL, APR_HOOK_MIDDLE);
* }
* static void myExt_post_config (apr_pool_t *p, apr_pool_t *plog,
* apr_pool_t *ptemp, server_rec *s)
* {
* httpExt_register_extension(p, MYEXT_URI,
* &myExt_notify, HTTP_EXT_EXCLUSIVE);
* }
*/
httpExt_error_code httpExt_register_extension (apr_pool_t *p, const char * const declName, httpExt_notify_t * notify, httpExt_handler_contract contract)
{
const char * lDeclName;
declType_t declType;
extension_t * extension;
if (ap_strchr((char *)declName, ':')) {
lDeclName = declName;
declType = URI;
} else {
lDeclName = apr_pstrdup(p, declName);
ap_str_tolower((char *)lDeclName);
declType = FIELD_NAME;
}
extension = (extension_t *)apr_hash_get(Extensions, lDeclName, APR_HASH_KEY_STRING);
if (extension == NULL || extension->contract == HTTP_EXT_NAK) {
extension = (extension_t *) apr_pcalloc(p, sizeof(extension_t));
extension->declType = declType;
extension->notify = notify;
apr_hash_set(Extensions, lDeclName, APR_HASH_KEY_STRING, extension);
return HTTP_EXT_OK;
} else {
if (extension->contract == HTTP_EXT_SHARED && contract == HTTP_EXT_SHARED)
/* !!! need to stack them. */
return HTTP_EXT_OK;
return HTTP_EXT_CONFLICT;
}
}
/* httpExt_get_headers_in - returns a table of headers associated with declName:
*
* apr_table_t * hIn = httpExt_get_headers_in (request_rec *r, MYEXT_URI);
*/
apr_table_t * httpExt_get_headers_in (request_rec *r, const char * const declName)
{
httpExt_reqContext_t *ctx = _httpExt_getReqContext(r);
apr_hash_t *instancesIn = ctx->instancesIn;
instance_t * instance = (instance_t *)apr_hash_get(instancesIn, declName, APR_HASH_KEY_STRING);
return instance == NULL ? NULL : instance->headers;
}
/* httpExt_register_response - inform HTTP extensions engine that a module
* will be using HTTP extensions in a response.
*
* httpExt_register_response(r, MYEXT_URI, HTTP_EXT_EXCLUSIVE, isMandatory,
* isConnectionExt, myHeaders, NULL, &httpExt_context);
*/
httpExt_error_code httpExt_register_response (request_rec *r, const char * const declName, httpExt_handler_contract contract, int mandatory, int connection, apr_table_t * headers, apr_table_t * declExts, void **pContext)
{
httpExt_reqContext_t *ctx = _httpExt_getReqContext(r);
instance_t * instance;
instance = (instance_t *)apr_hash_get(ctx->instancesOut, declName, APR_HASH_KEY_STRING);
if (instance == NULL) {
instance = (instance_t *) apr_pcalloc(r->pool, sizeof(instance_t));
apr_hash_set(ctx->instancesOut, declName, APR_HASH_KEY_STRING, instance);
instance->mandatory = mandatory;
instance->connection = connection;
instance->headers = headers;
instance->declExts = declExts;
instance->contract = contract;
instance->ns = apr_pcalloc(r->pool, 3);
sprintf(instance->ns, "%02d", ctx->nextNsOut++);
if (headers) {
apr_array_header_t * hdrs_arr = apr_table_elts(headers);
apr_table_entry_t * hdrs = (apr_table_entry_t *) hdrs_arr->elts;
int i;
for (i = 0; i < hdrs_arr->nelts; ++i) {
httpExt_set_header_out(instance, r, hdrs[i].key, hdrs[i].val);
}
}
} else {
/* !!! Ugh, what now? */
}
if (pContext)
*pContext = (void *)instance;
return HTTP_EXT_OK;
}
/* httpExt_set_header_out - set an HTTP extension header. Headers may also
* be set when the module calls httpExt_register_response.
*
* httpExt_set_header_out(context->httpExt_context, r, "My-ext-header", val)
*/
void httpExt_set_header_out (void * context, request_rec * r, const char * const key, const char * const value)
{
instance_t * instance = (instance_t *) context;
char * headerOut = apr_pstrcat(r->pool, instance->ns, "-", key, NULL);
apr_table_setn(r->headers_out, headerOut, value);
}
/* ERROR-REPORTING HELPER FUNCTIONS */
int httpExt_header_error (request_rec *r, const char * const name, const char * const value) {
LOGRETURN(HTTP_BAD_REQUEST, r, name, value);
}
int httpExt_parameter_error (request_rec *r, const char * const name, const char * const value) {
LOGRETURN(HTTP_BAD_REQUEST, r, name, value);
}
int httpExt_required_error (request_rec *r, const char * const declName, const char * const ns) {
ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 999, r->server,
"http_ext: mandatory \"%s\" (%s) instance not met", declName, ns);
return HTTP_NOT_EXTENDED;
}
#if 0
modules/http/http_protocol.c terminate_header
#endif