/* ====================================================================
* Copyright (c) 2000, MeepZor Consulting.
* All rights reserved.
*
* The use and distribution of this code or document is
* governed by version 1.0.1 of the MeepZor Consulting Public
* Licence (MCPL), which may be found on the Internet at
* .
*
*
* $Id: mod_babble.c,v 1.5 2001/11/23 14:58:58 coar Exp $
*
* Abstract:
*+
* A test module for the Apache Web server. It simply fills a buffer
* with the appropriate number of bytes in a given pattern string, and
* then emits it as the response body. The content length field is
* included so the response is cacheable.
*-
*/
/*
* Apache babble-test module. This simply sends a single large chunk
* of content; the size is determined by directives and URL query info.
*
* The query-string keywords may be combined and separated by any
* non-alphanumeric character legal in URIs (such as '&' or ';').
*
* ?size=1000
* ?pattern=string
* ?size=1000;pattern=string
*
* The pattern URL keyword is ignored if the URL is within the scope of a
* 'BabblePatternLock On' directive. This is to prevent malicious use,
* such as a link on a page with a pattern value that might cause
* browsers problems when displayed. This is one of the reasons for the
* BabbleMaxSize directive; the other is to limit server resource
* consumption.
*/
#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"
#include "http_main.h"
#include "http_protocol.h"
#include "http_request.h"
#include "util_script.h"
#include "util_date.h"
#include
/*--------------------------------------------------------------------------*/
/* */
/* Private data declarations. */
/* */
/*--------------------------------------------------------------------------*/
/*
* Per-directory configuration record defining defaults and limits on
* the babble text.
*/
typedef struct mbbl_conf_t {
int maxsize; /* Maximum size of reponse body */
#define F_DEFAULT_MAXSIZE 8192
int defaultsize; /* Default size in this scope */
#define F_DEFAULT_DEFAULTSIZE 8192
const char *pattern; /* Pattern to replicate into response */
int plen; /* Length of the pattern */
int patternlocked; /* Ignore ?pattern= keywords */
#define F_UNSET 3 /* Marker for inheritance purposes */
int canstream; /* Permit ?size=-1 for endless babbling */
} mbbl_conf_t;
#define BUFSIZE 8192
/*
* Declare ourselves so the configuration routines can find and know us.
* We'll fill it in at the end of the module.
*/
module babble_module;
/*
* Command handler for BabbleMaxSize directive. Record the maximum
* number of babble bytes per request in this scope.
*/
static const char *cmd_maxsize(cmd_parms *cmd, void *mconfig, char *word1) {
mbbl_conf_t *dconf;
int size;
dconf = (mbbl_conf_t *) mconfig;
size = atoi(word1);
/*
if (size <= 0) {
return "illegal value for BabbleMaxSize directive";
}
*/
dconf->maxsize = size;
return NULL;
}
/*
* Command handler for BabbleDefaultSize directive, which sets the
* default number of babble bytes per request (if not overridden
* by the URL keywords). Maximised against the BabbleMaxSize
* setting at runtime. We can't do that here because we might not
* have encountered a BabbleMaxSize directive yet.
*/
static const char *cmd_defaultsize(cmd_parms *cmd, void *mconfig,
char *word1) {
mbbl_conf_t *dconf;
int size;
dconf = (mbbl_conf_t *) mconfig;
size = atoi(word1);
if (size <= 0) {
return "illegal value for BabbleDefaultSize directive";
}
dconf->defaultsize = size;
return NULL;
}
/*
* Command handler for the BabbleDefaultPattern directive. This
* pattern overrides the default default, which is "deadbeef".
* It in turn can be overridden by the 'pattern' URL keyword
* under the right conditions.
*/
static const char *cmd_defaultpattern(cmd_parms *cmd, void *mconfig,
char *word1) {
mbbl_conf_t *dconf;
int size;
dconf = (mbbl_conf_t *) mconfig;
size = strlen(word1);
if (size <= 0) {
return "illegal value for BabbleDefaultPattern directive";
}
dconf->plen = size;
dconf->pattern = (const char *) ap_pstrdup(cmd->pool, word1);
return NULL;
}
/*
* Command handler for the BabblePatternLock Boolean directive, which
* controls whether the 'pattern' URL keyword is interpreted or ignored.
*/
static const char *cmd_patternlock(cmd_parms *cmd, void *mconfig, int locked) {
mbbl_conf_t *dconf;
dconf = (mbbl_conf_t *) mconfig;
dconf->patternlocked = locked ? 1 : 0;
return NULL;
}
/*
* Command handler for the BabbleStreaming Boolean directive, which
* controls whether infinite babbling (triggered by a URL 'size=-1'
* keyword) is enabled.
*/
static const char *cmd_streaming(cmd_parms *cmd, void *mconfig,
int canstream) {
mbbl_conf_t *dconf;
dconf = (mbbl_conf_t *) mconfig;
dconf->canstream = canstream ? 1 : 0;
return NULL;
}
/*
* This function gets called to create up a per-directory configuration
* record. This will be called for the "default" server environment, and for
* each directory for which the parser finds any of our directives applicable.
* If a directory doesn't have any of our directives involved (i.e., they
* aren't in the .htaccess file, or a , , or related
* block), this routine will *not* be called - the configuration for the
* closest ancestor is used.
*
* The return value is a pointer to the created module-specific
* structure.
*/
static void *mbbl_create_dconf(pool *p, char *dirspec) {
mbbl_conf_t *dconf;
/*
* Allocate the space for our record from the pool supplied.
*/
dconf = (mbbl_conf_t *) ap_pcalloc(p, sizeof(mbbl_conf_t));
/*
* Now fill in the defaults. If there are any 'parent' configuration
* records, they'll get merged as part of a separate callback.
*/
dconf->maxsize = F_DEFAULT_MAXSIZE;
dconf->defaultsize = F_DEFAULT_DEFAULTSIZE;
dconf->pattern = NULL;
dconf->plen = 0;
dconf->patternlocked = F_UNSET;
dconf->canstream = F_UNSET;
return (void *) dconf;
}
/*
* This function gets called to merge two per-directory configuration
* records. This is typically done to cope with things like .htaccess files
* or directives for directories that are beneath one for which a
* configuration record was already created. The routine has the
* responsibility of creating a new record and merging the contents of the
* other two into it appropriately. If the module doesn't declare a merge
* routine, the record for the closest ancestor location (that has one) is
* used exclusively.
*
* The routine MUST NOT modify any of its arguments!
*
* The return value is a pointer to the created module-specific structure
* containing the merged values.
*/
static void *mbbl_merge_dconf(pool *p, void *parent_conf, void *newloc_conf) {
mbbl_conf_t *merged_config;
mbbl_conf_t *pconf = (mbbl_conf_t *) parent_conf;
mbbl_conf_t *nconf = (mbbl_conf_t *) newloc_conf;
merged_config = (mbbl_conf_t *) ap_pcalloc(p, sizeof(mbbl_conf_t));
/*
* Inherit the settings from the closest config, or from the parent
* if the closest one doesn't have an explicit setting. Zero is
* the magic value indicating and unset field.
*/
if (nconf->maxsize != 0) {
merged_config->maxsize = nconf->maxsize;
}
else if ((pconf != NULL) && (pconf->maxsize != 0)) {
merged_config->maxsize = pconf->maxsize;
}
else {
merged_config->maxsize = F_DEFAULT_MAXSIZE;
}
/*
* Repeat for defaultsize.
*/
if (nconf->defaultsize != 0) {
merged_config->defaultsize = nconf->defaultsize;
}
else if ((pconf != NULL) && (pconf->defaultsize != 0)) {
merged_config->defaultsize = pconf->defaultsize;
}
else {
merged_config->defaultsize = merged_config->maxsize;
}
/*
* And again for the pattern, which uses a NULL pointer as a
* flag for unsetness.
*/
if (nconf->pattern != NULL) {
merged_config->pattern = nconf->pattern;
}
else if (pconf->pattern != NULL) {
merged_config->pattern = pconf->pattern;
}
else {
merged_config->pattern = "deadbeef";
}
merged_config->plen = strlen(merged_config->pattern);
/*
* Finally the patternlocked field. Since this is an int used as
* a Boolean, we need to use a magic value that's different
* from the ones used by the command handler.
*/
if (nconf->patternlocked != F_UNSET) {
merged_config->patternlocked = nconf->patternlocked;
}
else if (pconf->patternlocked != F_UNSET) {
merged_config->patternlocked = pconf->patternlocked;
}
else {
merged_config->patternlocked = 0;
}
/*
* Now the can-babble-forever flag.
*/
if (nconf->canstream != F_UNSET) {
merged_config->canstream = nconf->canstream;
}
else if (pconf->canstream != F_UNSET) {
merged_config->canstream = pconf->canstream;
}
else {
merged_config->canstream = 0;
}
return (void *) merged_config;
}
/*--------------------------------------------------------------------------*/
/* Now we declare our content handlers, which are invoked when the server */
/* encounters a document which our module is supposed to have a chance to */
/* see. */
/*--------------------------------------------------------------------------*/
/*
* Figure out what the parameters should be for the response to this request.
* Get the defaults from the config record, and adjust them according
* to any keywords in the query_string.
*/
static mbbl_conf_t *getparams(request_rec *r) {
mbbl_conf_t *dconf;
mbbl_conf_t *rconf;
int size;
char *ptr;
char delim[2];
ptr = NULL;
delim[0] = '\0';
delim[1] = '\0';
dconf = (mbbl_conf_t *) ap_get_module_config(r->per_dir_config,
&babble_module);
/*
* If there aren't any keywords in the query string, or the only
* ones are to be ignored, return the preset config record.
*/
if ((r->args == NULL) || (strlen(r->args) == 0)
|| (((ptr = strstr(r->args, "size=")) == NULL)
&& (dconf->patternlocked
|| ((! dconf->patternlocked)
&& (strstr(r->args, "pattern=") == NULL))))) {
return dconf;
}
/*
* We've got something, so copy config settings as defaults.
*/
rconf = ap_pcalloc(r->pool, sizeof(mbbl_conf_t));
rconf->maxsize = dconf->maxsize;
rconf->defaultsize = dconf->defaultsize;
rconf->pattern = dconf->pattern;
rconf->plen = dconf->plen;
/*
* If we have a size=n keyword, deal with it. ptr was set in the
* previous condition test. If it isn't the first part of the
* query string, take the character immediately preceding as
* the delimiter if it isn't alphanumeric.
*/
if ((ptr != NULL)
&& ((ptr == r->args) || (! ap_isalnum(ptr[-1])))) {
size = atoi(&ptr[5]);
if ((size < 0) && (! dconf->canstream)) {
size = dconf->maxsize;
}
if (size <= dconf->maxsize) {
rconf->defaultsize = size;
}
if (ptr > r->args) {
delim[0] = ptr[-1];
}
}
/*
* Repeat the process for the pattern keyword if it's not being
* ignored. Once again we try to determine delimiter from the
* byte immediately preceding the keyword IFF we haven't already
* gotten one.
*/
if ((! dconf->patternlocked)
&& ((ptr = strstr(r->args, "pattern=")) != NULL)
&& ((ptr == r->args)
|| (! ap_isalnum(ptr[-1])))) {
if ((delim[0] == '\0') && (ptr > r->args)) {
delim[0] = ptr[-1];
}
rconf->pattern = ap_pstrdup(r->pool, &ptr[8]);
/*
* Here's why we care about the delimiter: so we can correctly
* truncate the pattern for query values like "?pattern=x&size=2".
*/
if (delim[0] != '\0') {
ptr = strstr(rconf->pattern, delim);
if (ptr != NULL) {
*ptr = '\0';
}
}
rconf->plen = strlen(rconf->pattern);
}
return rconf;
}
/*
* The guts of the module: the content handler. Create a buffer of the
* appropriate size, fill it with the specified pattern, and dump it.
*
* The return value instructs the caller concerning what happened and what to
* do next:
* OK ("we did our thing")
* DECLINED ("this isn't something with which we want to get involved")
* HTTP_mumble ("an error status should be reported")
*/
static int mbbl_handler(request_rec *r) {
mbbl_conf_t *dconf;
char *body;
char *seg;
int seglen;
int remaining;
int bufsize;
int rstat;
dconf = getparams(r);
if (dconf->defaultsize > 0) {
ap_set_content_length(r, dconf->defaultsize);
}
r->content_type = "text/plain";
ap_hard_timeout("send babble output", r);
ap_send_http_header(r);
/*
* If we're only supposed to send header information (HEAD request), we're
* already there.
*/
if (r->header_only) {
ap_kill_timeout(r);
return OK;
}
/*
* Allocate a buffer (maximum BUFSIZE bytes) for the content; add a
* byte for the terminating NUL character.
*/
bufsize = ((BUFSIZE / dconf->plen) * dconf->plen) + 1;
if (bufsize < dconf->plen) {
bufsize += dconf->plen;
}
body = (char *) ap_palloc(r->pool, bufsize);
/*
* Replicate the string into the buffer as many times as it will
* fit, and then hang the NUL on the end.
*/
remaining = bufsize - 1;
seg = body;
while (remaining > 0) {
seglen = (remaining < (dconf->plen)) ? remaining : (dconf->plen);
ap_cpystrn(seg, dconf->pattern, seglen + 1);
remaining -= seglen;
seg = &seg[seglen];
}
/*
* body should now point to a NUL-terminated string with an integral
* number of copies of the pattern in it.
*/
/*
* Now send our actual output, resending the buffer as many times as
* necessary.
*/
remaining = dconf->defaultsize;
while ((unsigned int) remaining > (bufsize - 1)) {
rstat = ap_rputs(body, r);
if (rstat == EOF) {
break;
}
remaining -= (bufsize - 1);
}
if (rstat != EOF) {
/*
* Only a partial (or possibly one complete) buffer left to write.
* Make sure we're NUL-terminated before writing it.
*/
body[remaining] = '\0';
rstat = ap_rputs(body, r);
}
/*
* We're all done, so cancel the timeout we set. Since this is probably
* the end of the request we *could* assume this would be done during
* post-processing - but it's possible that another handler might be
* called and inherit our outstanding timer. Not good; to each its own.
*/
ap_kill_timeout(r);
return OK;
}
/*--------------------------------------------------------------------------*/
/* */
/* All of the routines have been declared now */
/* */
/*--------------------------------------------------------------------------*/
/*
* List of directives specific to our module.
*/
static const command_rec mbbl_cmds[] =
{
{
"BabbleMaxSize", /* directive name */
cmd_maxsize, /* config action routine */
NULL, /* argument to include in call */
ACCESS_CONF, /* where available */
TAKE1, /* arguments */
"maximum number of bytes in response"
/* directive description */
},
{
"BabbleDefaultSize", /* directive name */
cmd_defaultsize, /* config action routine */
NULL, /* argument to include in call */
OR_OPTIONS, /* where available */
TAKE1, /* arguments */
"default number of bytes in response"
/* directive description */
},
{
"BabbleDefaultPattern", /* directive name */
cmd_defaultpattern, /* config action routine */
NULL, /* argument to include in call */
OR_OPTIONS, /* where available */
TAKE1, /* arguments */
"pattern string to be replicated to comprise"
/* directive description */
},
{
"BabblePatternLock", /* directive name */
cmd_patternlock, /* config action routine */
NULL, /* argument to include in call */
OR_OPTIONS, /* where available */
FLAG, /* arguments */
"flag indicating whether ?pattern= should be ignored"
/* directive description */
},
{
"BabbleStreaming", /* directive name */
cmd_streaming, /* config action routine */
NULL, /* argument to include in call */
ACCESS_CONF, /* where available */
FLAG, /* arguments */
"flag indicating whether ?size=-1 infinite babbling is allowed"
/* directive description */
},
{ NULL }
};
/*--------------------------------------------------------------------------*/
/* */
/* Now the list of content handlers available from this module. */
/* */
/*--------------------------------------------------------------------------*/
/*
* List of content handlers our module supplies. Each handler is defined by
* two parts: a name by which it can be referenced (such as by
* {Add,Set}Handler), and the actual routine name. The list is terminated by
* a NULL block, since it can be of variable length.
*
* Note that content-handlers are invoked on a most-specific to least-specific
* basis; that is, a handler that is declared for "text/plain" will be
* invoked before one that was declared for "text / *". Note also that
* if a content-handler returns anything except DECLINED, no other
* content-handlers will be called.
*/
static const handler_rec mbbl_handlers[] =
{
{ "babbler", mbbl_handler },
{ NULL }
};
/*--------------------------------------------------------------------------*/
/* */
/* Finally, the list of callback routines and data structures that */
/* provide the hooks into our module from the other parts of the server. */
/* */
/*--------------------------------------------------------------------------*/
/*
* Module definition for configuration. If a particular callback is not
* needed, its slot contains NULL.
*
*/
module babble_module =
{
STANDARD_MODULE_STUFF,
NULL, /* module initializer */
mbbl_create_dconf, /* per-directory config creator */
mbbl_merge_dconf, /* dir config merger */
NULL, /* server config creator */
NULL, /* server config merger */
mbbl_cmds, /* command table */
mbbl_handlers, /* [7] list of handlers */
NULL, /* [2] filename-to-URI translation */
NULL, /* [5] check/validate user_id */
NULL, /* [6] check user_id is valid *here* */
NULL, /* [4] check access by host address */
NULL, /* [7] MIME type checker/setter */
NULL, /* [8] fixups */
NULL, /* [10] logger */
#if MODULE_MAGIC_NUMBER >= 19970103
NULL, /* [3] header parser */
#endif
#if MODULE_MAGIC_NUMBER >= 19970719
NULL, /* process initializer */
#endif
#if MODULE_MAGIC_NUMBER >= 19970728
NULL, /* process exit/cleanup */
#endif
#if MODULE_MAGIC_NUMBER >= 19970902
NULL /* [1] post read_request handling */
#endif
};
/*
* Emacs settings
*
* Local Variables:
* mode: C
* c-file-style: "bsd"
* End:
*/