summaryrefslogtreecommitdiff
path: root/modules/http
diff options
context:
space:
mode:
authorStefan Eissing <icing@apache.org>2022-04-13 07:40:17 +0000
committerStefan Eissing <icing@apache.org>2022-04-13 07:40:17 +0000
commitfbb84e00fa53ca34b97f9acc4d024e512d4e6f23 (patch)
tree47249fbbc06ebbf64e767683f870c0d7dd14213f /modules/http
parentd150ca6f4e93622150fd4867e7f7c1eec0026acb (diff)
downloadhttpd-fbb84e00fa53ca34b97f9acc4d024e512d4e6f23.tar.gz
Merge PR 311:
*) core/mod_http: use REQUEST meta buckets and a new HTTP/1.x specific input filter to separate the handling for HTTP requests from the handling of HTTP/1.x request parsing and checks. A new HTTP1_REQUEST_IN filter installs itself on http/1.1 connections before a request is being read. It generates either a REQUEST meta bucket on success or an ERROR bucket with the proposed response status. The core connection processing, relying on ap_read_request(), now expects a REQUEST or ERROR bucket from the input filters and is agnostic to specific HTTP versions and how they bring requests into the server. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1899799 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules/http')
-rw-r--r--modules/http/http_core.c63
-rw-r--r--modules/http/http_filters.c193
-rw-r--r--modules/http/http_protocol.c202
3 files changed, 451 insertions, 7 deletions
diff --git a/modules/http/http_core.c b/modules/http/http_core.c
index 92ab08911b..3b94270554 100644
--- a/modules/http/http_core.c
+++ b/modules/http/http_core.c
@@ -24,6 +24,7 @@
#include "http_config.h"
#include "http_connection.h"
#include "http_core.h"
+#include "http_log.h"
#include "http_protocol.h" /* For index_of_response(). Grump. */
#include "http_request.h"
@@ -36,6 +37,7 @@
/* Handles for core filters */
AP_DECLARE_DATA ap_filter_rec_t *ap_http_input_filter_handle;
+AP_DECLARE_DATA ap_filter_rec_t *ap_h1_request_in_filter_handle;
AP_DECLARE_DATA ap_filter_rec_t *ap_h1_body_in_filter_handle;
AP_DECLARE_DATA ap_filter_rec_t *ap_http_header_filter_handle;
AP_DECLARE_DATA ap_filter_rec_t *ap_h1_response_out_filter_handle;
@@ -269,15 +271,66 @@ static int http_create_request(request_rec *r)
return OK;
}
-static void http_pre_read_request(request_rec *r, conn_rec *c)
+static void h1_pre_read_request(request_rec *r, conn_rec *c)
{
if (!r->main && !r->prev
&& !strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
+ if (r->proxyreq == PROXYREQ_NONE) {
+ ap_add_input_filter_handle(ap_h1_request_in_filter_handle,
+ NULL, r, r->connection);
+ }
ap_add_output_filter_handle(ap_h1_response_out_filter_handle,
NULL, r, r->connection);
}
}
+static int h1_post_read_request(request_rec *r)
+{
+ const char *tenc;
+
+ if (!r->main && !r->prev && r->proto_num <= HTTP_VERSION(1,1)) {
+ if (r->proto_num >= HTTP_VERSION(1,0)) {
+ tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+ if (tenc) {
+ r->body_indeterminate = 1;
+
+ /* https://tools.ietf.org/html/rfc7230
+ * Section 3.3.3.3: "If a Transfer-Encoding header field is
+ * present in a request and the chunked transfer coding is not
+ * the final encoding ...; the server MUST respond with the 400
+ * (Bad Request) status code and then close the connection".
+ */
+ if (!ap_is_chunked(r->pool, tenc)) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02539)
+ "client sent unknown Transfer-Encoding "
+ "(%s): %s", tenc, r->uri);
+ return HTTP_BAD_REQUEST;
+ }
+
+ /* https://tools.ietf.org/html/rfc7230
+ * Section 3.3.3.3: "If a message is received with both a
+ * Transfer-Encoding and a Content-Length header field, the
+ * Transfer-Encoding overrides the Content-Length. ... A sender
+ * MUST remove the received Content-Length field".
+ */
+ if (apr_table_get(r->headers_in, "Content-Length")) {
+ apr_table_unset(r->headers_in, "Content-Length");
+
+ /* Don't reuse this connection anyway to avoid confusion with
+ * intermediaries and request/reponse spltting.
+ */
+ r->connection->keepalive = AP_CONN_CLOSE;
+ }
+ }
+ }
+ /* HTTP1_BODY_IN takes care of chunked encoding and content-length.
+ */
+ ap_add_input_filter_handle(ap_h1_body_in_filter_handle,
+ NULL, r, r->connection);
+ }
+ return OK;
+}
+
static int http_send_options(request_rec *r)
{
if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') &&
@@ -308,11 +361,17 @@ static void register_hooks(apr_pool_t *p)
ap_hook_map_to_storage(http_send_options,NULL,NULL,APR_HOOK_MIDDLE);
ap_hook_http_scheme(http_scheme,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_default_port(http_port,NULL,NULL,APR_HOOK_REALLY_LAST);
+
ap_hook_create_request(http_create_request, NULL, NULL, APR_HOOK_REALLY_LAST);
- ap_hook_pre_read_request(http_pre_read_request, NULL, NULL, APR_HOOK_REALLY_LAST);
+ ap_hook_pre_read_request(h1_pre_read_request, NULL, NULL, APR_HOOK_REALLY_LAST);
+ ap_hook_post_read_request(h1_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST);
+
ap_http_input_filter_handle =
ap_register_input_filter("HTTP_IN", ap_http_filter,
NULL, AP_FTYPE_PROTOCOL);
+ ap_h1_request_in_filter_handle =
+ ap_register_input_filter("HTTP1_REQUEST_IN", ap_h1_request_in_filter,
+ NULL, AP_FTYPE_PROTOCOL);
ap_h1_body_in_filter_handle =
ap_register_input_filter("HTTP1_BODY_IN", ap_h1_body_in_filter,
NULL, AP_FTYPE_TRANSCODE);
diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c
index 35fad461f6..42a89cbea4 100644
--- a/modules/http/http_filters.c
+++ b/modules/http/http_filters.c
@@ -1856,12 +1856,12 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f,
ap_bucket_response *resp = e->data;
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
- "ap_http1_response_out_filter seeing response bucket status=%d",
+ "ap_h1_response_out_filter seeing response bucket status=%d",
resp->status);
if (strict && resp->status < 100) {
/* error, not a valid http status */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10386)
- "ap_http1_response_out_filter seeing headers "
+ "ap_h1_response_out_filter seeing headers "
"status=%d in strict mode",
resp->status);
rv = AP_FILTER_ERROR;
@@ -1871,7 +1871,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f,
/* already sent the final response for the request.
*/
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10387)
- "ap_http1_response_out_filter seeing headers "
+ "ap_h1_response_out_filter seeing headers "
"status=%d after final response already sent",
resp->status);
rv = AP_FILTER_ERROR;
@@ -1926,7 +1926,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f,
rv = ap_pass_brigade(f->next, b);
apr_brigade_cleanup(b);
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r,
- "ap_http1_response_out_filter passed response"
+ "ap_h1_response_out_filter passed response"
", add CHUNK filter");
if (APR_SUCCESS != rv) {
apr_brigade_cleanup(ctx->tmpbb);
@@ -1950,7 +1950,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f,
/* data buckets before seeing the final response are in error.
*/
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10390)
- "ap_http1_response_out_filter seeing data before headers, %ld bytes ",
+ "ap_h1_response_out_filter seeing data before headers, %ld bytes ",
(long)e->length);
rv = AP_FILTER_ERROR;
goto cleanup;
@@ -2080,3 +2080,186 @@ static apr_bucket *create_trailers_bucket(request_rec *r, apr_bucket_alloc_t *bu
}
return NULL;
}
+
+typedef struct h1_request_ctx {
+ enum
+ {
+ REQ_LINE, /* reading 1st request line */
+ REQ_HEADERS, /* reading header lines */
+ REQ_BODY, /* reading body follows, terminal */
+ REQ_ERROR, /* failed, terminal */
+ } state;
+
+ request_rec *r;
+ char *request_line;
+ const char *method;
+ const char *uri;
+ const char *protocol;
+} h1_request_ctx;
+
+static apr_status_t read_request_line(h1_request_ctx *ctx, apr_bucket_brigade *bb)
+{
+ apr_size_t len;
+ int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES;
+ core_server_config *conf = ap_get_core_module_config(ctx->r->server->module_config);
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+ apr_status_t rv;
+
+ /* Read past empty lines until we get a real request line,
+ * a read error, the connection closes (EOF), or we timeout.
+ *
+ * We skip empty lines because browsers have to tack a CRLF on to the end
+ * of POSTs to support old CERN webservers. But note that we may not
+ * have flushed any previous response completely to the client yet.
+ * We delay the flush as long as possible so that we can improve
+ * performance for clients that are pipelining requests. If a request
+ * is pipelined then we won't block during the (implicit) read() below.
+ * If the requests aren't pipelined, then the client is still waiting
+ * for the final buffer flush from us, and we will block in the implicit
+ * read(). B_SAFEREAD ensures that the BUFF layer flushes if it will
+ * have to block during a read.
+ */
+ do {
+ /* ensure ap_rgetline allocates memory each time thru the loop
+ * if there are empty lines
+ */
+ ctx->request_line = NULL;
+ len = 0;
+ rv = ap_rgetline(&ctx->request_line, (apr_size_t)(ctx->r->server->limit_req_line + 2),
+ &len, ctx->r, strict ? AP_GETLINE_CRLF : 0, bb);
+
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ else if (len > 0) {
+ /* got the line in ctx->r->the_request */
+ return APR_SUCCESS;
+ }
+ } while (--num_blank_lines >= 0);
+ /* too many blank lines */
+ return APR_EINVAL;
+}
+
+static void sanitize_brigade(apr_bucket_brigade *bb)
+{
+ apr_bucket *e, *next;
+
+ for (e = APR_BRIGADE_FIRST(bb);
+ e != APR_BRIGADE_SENTINEL(bb);
+ e = next)
+ {
+ next = APR_BUCKET_NEXT(e);
+ if (!APR_BUCKET_IS_METADATA(e) && e->length == 0) {
+ apr_bucket_delete(e);
+ }
+ }
+}
+
+apr_status_t ap_h1_request_in_filter(ap_filter_t *f,
+ apr_bucket_brigade *bb,
+ ap_input_mode_t mode,
+ apr_read_type_e block,
+ apr_off_t readbytes)
+{
+ request_rec *r = f->r;
+ apr_bucket *e;
+ h1_request_ctx *ctx = f->ctx;
+ apr_status_t rv = APR_SUCCESS;
+ int http_status = HTTP_OK;
+
+ /* just get out of the way for things we don't want to handle. */
+ if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
+ return ap_get_brigade(f->next, bb, mode, block, readbytes);
+ }
+
+ if (!ctx) {
+ f->ctx = ctx = apr_pcalloc(r->pool, sizeof(*ctx));
+ ctx->r = r;
+ ctx->state = REQ_LINE;
+ }
+
+ /* This filter needs to get out of the way of read_request_line() */
+ ap_remove_input_filter(f);
+
+ while (APR_SUCCESS == rv) {
+ switch (ctx->state) {
+ case REQ_LINE:
+ if ((rv = read_request_line(ctx, bb)) != APR_SUCCESS) {
+ /* certain failures are answered with a HTTP error bucket
+ * and are terminal for parsing a request */
+ ctx->method = ctx->uri = "-";
+ ctx->protocol = "HTTP/1.0";
+ if (APR_STATUS_IS_ENOSPC(rv)) {
+ http_status = HTTP_REQUEST_URI_TOO_LARGE;
+ }
+ else if (APR_STATUS_IS_TIMEUP(rv)) {
+ http_status = HTTP_REQUEST_TIME_OUT;
+ }
+ else if (APR_STATUS_IS_BADARG(rv)) {
+ http_status = HTTP_BAD_REQUEST;
+ }
+ else if (APR_STATUS_IS_EINVAL(rv)) {
+ http_status = HTTP_BAD_REQUEST;
+ }
+ goto cleanup;
+ }
+
+ if (!ap_h1_tokenize_request_line(r, ctx->request_line,
+ &ctx->method, &ctx->uri, &ctx->protocol)) {
+ http_status = HTTP_BAD_REQUEST;
+ goto cleanup;
+ }
+ /* got the request line and it looked to contain what we need */
+ ctx->state = REQ_HEADERS;
+ break;
+
+ case REQ_HEADERS:
+ ap_get_mime_headers_core(r, bb);
+ if (r->status != HTTP_OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567)
+ "request failed: error reading the headers");
+ http_status = r->status;
+ goto cleanup;
+ }
+ /* clear the brigade, as ap_get_mime_headers_core() leaves the last
+ * empty line in there, insert the REQUEST bucket and return */
+ apr_brigade_cleanup(bb);
+ e = ap_bucket_request_createn(ctx->method, ctx->uri,
+ ctx->protocol, r->headers_in,
+ r->pool, r->connection->bucket_alloc);
+ /* reading may leave 0 length data buckets in the brigade,
+ * get rid of those. */
+ sanitize_brigade(bb);
+ APR_BRIGADE_INSERT_HEAD(bb, e);
+ ctx->state = REQ_BODY;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
+ "http1 request and headers parsed: %s %s %s",
+ ctx->method, ctx->uri, ctx->protocol);
+ goto cleanup;
+
+ case REQ_BODY:
+ /* we should not come here */
+ AP_DEBUG_ASSERT(0);
+ rv = ap_get_brigade(f->next, bb, mode, block, readbytes);
+ goto cleanup;
+
+ case REQ_ERROR:
+ default:
+ rv = APR_EINVAL;
+ goto cleanup;
+ }
+ } /* while(APR_SUCCESS == rv) */
+
+cleanup:
+ if (http_status != HTTP_OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+ "failed reading request line, returning error bucket %d", http_status);
+ apr_brigade_cleanup(bb);
+ e = ap_bucket_error_create(http_status, NULL, r->pool,
+ f->c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, e);
+ ctx->state = REQ_ERROR;
+ return APR_SUCCESS;
+ }
+ return rv;
+} \ No newline at end of file
diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c
index 9335670052..e324bbcbb1 100644
--- a/modules/http/http_protocol.c
+++ b/modules/http/http_protocol.c
@@ -1636,3 +1636,205 @@ AP_DECLARE(void) ap_h1_add_end_chunk(apr_bucket_brigade *b,
if (tmp) APR_BRIGADE_CONCAT(b, tmp);
}
}
+
+typedef enum {
+ rrl_none, rrl_badprotocol, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace,
+ rrl_missinguri, rrl_baduri, rrl_trailingtext,
+} rrl_error;
+
+/* get the length of a name for logging, but no more than 80 bytes */
+#define LOG_NAME_MAX_LEN 80
+static int log_name_len(const char *name)
+{
+ apr_size_t len = strlen(name);
+ return (len > LOG_NAME_MAX_LEN)? LOG_NAME_MAX_LEN : (int)len;
+}
+
+static void rrl_log_error(request_rec *r, rrl_error error, const char *etoken)
+{
+ switch (error) {
+ case rrl_none:
+ break;
+ case rrl_badprotocol:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418)
+ "HTTP Request Line; Unrecognized protocol '%.*s' "
+ "(perhaps whitespace was injected?)",
+ log_name_len(etoken), etoken);
+ break;
+ case rrl_badmethod:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03445)
+ "HTTP Request Line; Invalid method token: '%.*s'",
+ log_name_len(etoken), etoken);
+ break;
+ case rrl_badwhitespace:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03447)
+ "HTTP Request Line; Invalid whitespace");
+ break;
+ case rrl_excesswhitespace:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03448)
+ "HTTP Request Line; Excess whitespace "
+ "(disallowed by HttpProtocolOptions Strict)");
+ break;
+ case rrl_missinguri:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03446)
+ "HTTP Request Line; Missing URI");
+ case rrl_baduri:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03454)
+ "HTTP Request Line; URI incorrectly encoded: '%.*s'",
+ log_name_len(etoken), etoken);
+ break;
+ case rrl_trailingtext:
+ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03449)
+ "HTTP Request Line; Extraneous text found '%.*s' "
+ "(perhaps whitespace was injected?)",
+ log_name_len(etoken), etoken);
+ break;
+ }
+}
+
+/* remember the first error we encountered during tokenization */
+#define RRL_ERROR(e, et, y, yt) \
+ do { \
+ if (e == rrl_none) {\
+ e = y; et = yt;\
+ }\
+ } while (0)
+
+static rrl_error tokenize_request_line(
+ char *line, int strict,
+ const char **pmethod, const char **puri, const char **pprotocol,
+ const char **perror_token)
+{
+ char *method, *protocol, *uri, *ll;
+ rrl_error e = rrl_none;
+ char *etoken = NULL;
+ apr_size_t len = 0;
+
+ method = line;
+ /* If there is whitespace before a method, skip it and mark in error */
+ if (apr_isspace(*method)) {
+ RRL_ERROR(e, etoken, rrl_badwhitespace, method);
+ for ( ; apr_isspace(*method); ++method)
+ ;
+ }
+
+ /* Scan the method up to the next whitespace, ensure it contains only
+ * valid http-token characters, otherwise mark in error
+ */
+ if (strict) {
+ ll = (char*) ap_scan_http_token(method);
+ }
+ else {
+ ll = (char*) ap_scan_vchar_obstext(method);
+ }
+
+ if ((ll == method) || (*ll && !apr_isspace(*ll))) {
+ RRL_ERROR(e, etoken, rrl_badmethod, ll);
+ ll = strpbrk(ll, "\t\n\v\f\r ");
+ }
+
+ /* Verify method terminated with a single SP, or mark as specific error */
+ if (!ll) {
+ RRL_ERROR(e, etoken, rrl_missinguri, NULL);
+ protocol = uri = "";
+ goto done;
+ }
+ else if (strict && ll[0] && apr_isspace(ll[1])) {
+ RRL_ERROR(e, etoken, rrl_excesswhitespace, ll);
+ }
+
+ /* Advance uri pointer over leading whitespace, NUL terminate the method
+ * If non-SP whitespace is encountered, mark as specific error
+ */
+ for (uri = ll; apr_isspace(*uri); ++uri)
+ if (*uri != ' ')
+ RRL_ERROR(e, etoken, rrl_badwhitespace, uri);
+ *ll = '\0';
+
+ if (!*uri)
+ RRL_ERROR(e, etoken, rrl_missinguri, NULL);
+
+ /* Scan the URI up to the next whitespace, ensure it contains no raw
+ * control characters, otherwise mark in error
+ */
+ ll = (char*) ap_scan_vchar_obstext(uri);
+ if (ll == uri || (*ll && !apr_isspace(*ll))) {
+ RRL_ERROR(e, etoken, rrl_baduri, ll);
+ ll = strpbrk(ll, "\t\n\v\f\r ");
+ }
+
+ /* Verify URI terminated with a single SP, or mark as specific error */
+ if (!ll) {
+ protocol = "";
+ goto done;
+ }
+ else if (strict && ll[0] && apr_isspace(ll[1])) {
+ RRL_ERROR(e, etoken, rrl_excesswhitespace, ll);
+ }
+
+ /* Advance protocol pointer over leading whitespace, NUL terminate the uri
+ * If non-SP whitespace is encountered, mark as specific error
+ */
+ for (protocol = ll; apr_isspace(*protocol); ++protocol)
+ if (*protocol != ' ')
+ RRL_ERROR(e, etoken, rrl_badwhitespace, protocol);
+ *ll = '\0';
+
+ /* Scan the protocol up to the next whitespace, validation comes later */
+ if (!(ll = (char*) ap_scan_vchar_obstext(protocol))) {
+ len = strlen(protocol);
+ goto done;
+ }
+ len = ll - protocol;
+
+ /* Advance over trailing whitespace, if found mark in error,
+ * determine if trailing text is found, unconditionally mark in error,
+ * finally NUL terminate the protocol string
+ */
+ if (*ll && !apr_isspace(*ll)) {
+ RRL_ERROR(e, etoken, rrl_badprotocol, ll);
+ }
+ else if (strict && *ll) {
+ RRL_ERROR(e, etoken, rrl_excesswhitespace, ll);
+ }
+ else {
+ for ( ; apr_isspace(*ll); ++ll)
+ if (*ll != ' ') {
+ RRL_ERROR(e, etoken, rrl_badwhitespace, ll);
+ break;
+ }
+ if (*ll)
+ RRL_ERROR(e, etoken, rrl_trailingtext, ll);
+ }
+ *((char *)protocol + len) = '\0';
+
+done:
+ *pmethod = method;
+ *puri = uri;
+ *pprotocol = protocol;
+ *perror_token = etoken;
+ return e;
+}
+
+AP_DECLARE(int) ap_h1_tokenize_request_line(
+ request_rec *r, const char *line,
+ const char **pmethod, const char **puri, const char **pprotocol)
+{
+ core_server_config *conf = ap_get_core_module_config(r->server->module_config);
+ int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+ rrl_error error;
+ const char *error_token;
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "ap_tokenize_request_line: '%s'", line);
+ error = tokenize_request_line(apr_pstrdup(r->pool, line), strict, pmethod,
+ puri, pprotocol, &error_token);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r,
+ "ap_tokenize_request: error=%d, method=%s, uri=%s, protocol=%s",
+ error, *pmethod, *puri, *pprotocol);
+ if (error != rrl_none) {
+ rrl_log_error(r, error, error_token);
+ return 0;
+ }
+ return 1;
+}