/*
* Copyright (C) 2000, 2001, 2013 Gregory Trubetskoy
* Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* Originally developed by Gregory Trubetskoy.
*
*
* filterobject.c
*
*
* See accompanying documentation and source code comments
* for details.
*
*/
#include "mod_python.h"
/*** Some explanation of what is going on here:
*
* In Apache terminology, an "input" filter filters data flowing from
* network to application (aka "up"), an "output" filter filters data
* flowing from application to network (aka "down").
*
* An output filter is invoked as a result of ap_pass_brigade()
* call. It is given a populated brigade, which it then gives in the
* same fashion to the next filter via ap_pass_brigade(). (The filter
* may chose to create a new brigade and pass it instead).
*
* An input filter is invoked as a result of ap_get_brigade() call. It
* is given an empty brigade, which it is expected to populate, which
* may in turn require the filter to invoke the next filter in the
* same fashion (via ap_get_brigade()).
*
* In mod_python Output filters:
*
* filter.read() - copies data from *given* bucket brigade (saved in
* self->bb_in) into a Python string.
*
* filter.write() - copies data from a Python string into a *new*
* bucket brigade (saved in self->bb_out).
*
* filter.close() - appends an EOS and passes the self->bb_out brigade
* to the next filter via ap_pass_brigade()
*
* In mod_python Input filters:
*
* filter.read() - copies data from a *new* and *populated via
* ap_get_brigade* (saved as self->bb_in) into a Python string.
*
* filter.write() - copies data from a Python string into a *given*
* brigade (saved as self->bb_out).
*
* filter.close() - appends an EOS to *given* brigade.
*
*/
/**
** MpFilter_FromFilter
**
* This routine creates a Python filerobject.
*
*/
PyObject *MpFilter_FromFilter(ap_filter_t *f, apr_bucket_brigade *bb, int is_input,
ap_input_mode_t mode, apr_size_t readbytes,
char * handler, char *dir)
{
filterobject *result;
result = PyObject_New(filterobject, &MpFilter_Type);
if (! result)
return PyErr_NoMemory();
result->f = f;
result->is_input = is_input;
result->rc = APR_SUCCESS;
if (is_input) {
result->bb_in = NULL;
result->bb_out = bb;
result->mode = mode;
result->readbytes = readbytes;
}
else {
result->bb_in = bb;
result->bb_out = NULL;
result->mode = 0;
result->readbytes = 0;
}
result->closed = 0;
result->softspace = 0;
result->handler = handler;
result->dir = dir;
result->request_obj = NULL;
apr_pool_cleanup_register(f->r->pool, (PyObject *)result, python_decref,
apr_pool_cleanup_null);
return (PyObject *)result;
}
/**
** filter_pass_on
**
* just passes everything on
*/
static PyObject *filter_pass_on(filterobject *self)
{
Py_BEGIN_ALLOW_THREADS;
if (self->is_input)
self->rc = ap_get_brigade(self->f->next, self->bb_out,
self->mode, APR_BLOCK_READ,
self->readbytes);
else
self->rc = ap_pass_brigade(self->f->next, self->bb_in);
Py_END_ALLOW_THREADS;
Py_INCREF(Py_None);
return Py_None;
}
/**
** _filter_read
**
* read from filter - works for both read() and readline()
*/
static PyObject *_filter_read(filterobject *self, PyObject *args, int readline)
{
apr_bucket *b;
long bytes_read;
PyObject *result;
char *buffer;
long bufsize;
int newline = 0;
long len = -1;
conn_rec *c = self->request_obj->request_rec->connection;
if (! PyArg_ParseTuple(args, "|l", &len))
return NULL;
if (self->closed) {
PyErr_SetString(PyExc_ValueError, "I/O operation on closed filter");
return NULL;
}
if (self->is_input) {
/* does the output brigade exist? */
if (!self->bb_in) {
self->bb_in = apr_brigade_create(self->f->r->pool,
c->bucket_alloc);
}
Py_BEGIN_ALLOW_THREADS;
self->rc = ap_get_brigade(self->f->next, self->bb_in, self->mode,
APR_BLOCK_READ, self->readbytes);
Py_END_ALLOW_THREADS;
if (!APR_STATUS_IS_EAGAIN(self->rc) && !(self->rc == APR_SUCCESS)) {
PyErr_SetString(PyExc_IOError, "Input filter read error");
return NULL;
}
}
/*
* loop through the brigade reading buckets into the string
*/
b = APR_BRIGADE_FIRST(self->bb_in);
if (b == APR_BRIGADE_SENTINEL(self->bb_in))
return PyBytes_FromString("");
/* reached eos ? */
if (APR_BUCKET_IS_EOS(b)) {
apr_bucket_delete(b);
Py_INCREF(Py_None);
return Py_None;
}
bufsize = len < 0 ? HUGE_STRING_LEN : len;
result = PyBytes_FromStringAndSize(NULL, bufsize);
/* possibly no more memory */
if (result == NULL)
return PyErr_NoMemory();
buffer = PyBytes_AS_STRING((PyBytesObject *) result);
bytes_read = 0;
while ((bytes_read < len || len == -1) &&
!(APR_BUCKET_IS_EOS(b) || APR_BUCKET_IS_FLUSH(b) ||
b == APR_BRIGADE_SENTINEL(self->bb_in))) {
const char *data;
apr_size_t size;
apr_bucket *old;
int i;
if (apr_bucket_read(b, &data, &size, APR_BLOCK_READ) != APR_SUCCESS) {
PyErr_SetObject(PyExc_IOError,
PyBytes_FromString("Filter read error"));
return NULL;
}
if (bytes_read + size > bufsize) {
apr_bucket_split(b, bufsize - bytes_read);
size = bufsize - bytes_read;
/* now the bucket is the exact size we need */
}
if (readline) {
/* scan for newline */
for (i=0; irequest_obj->request_rec->connection;
if (self->closed) {
PyErr_SetString(PyExc_ValueError, "I/O operation on closed filter");
return NULL;
}
if (! PyArg_ParseTuple(args, "s#", &buff, &len))
return NULL; /* bad args */
if (len) {
/* does the output brigade exist? */
if (!self->bb_out)
self->bb_out = apr_brigade_create(self->f->r->pool,
c->bucket_alloc);
/* it looks like there is no need to memcpy, an immortal
bucket is fine, since Python won't free that memory before
the write is over */
b = apr_bucket_immortal_create(buff, len, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(self->bb_out, b);
}
Py_INCREF(Py_None);
return Py_None;
}
/**
** filter.flush(filter self)
**
* Flush output (i.e. pass brigade)
*/
static PyObject *filter_flush(filterobject *self, PyObject *args)
{
conn_rec *c = self->request_obj->request_rec->connection;
/* does the output brigade exist? */
if (!self->bb_out) {
self->bb_out = apr_brigade_create(self->f->r->pool,
c->bucket_alloc);
}
APR_BRIGADE_INSERT_TAIL(self->bb_out,
apr_bucket_flush_create(c->bucket_alloc));
if (!self->is_input) {
Py_BEGIN_ALLOW_THREADS;
self->rc = ap_pass_brigade(self->f->next, self->bb_out);
apr_brigade_destroy(self->bb_out);
Py_END_ALLOW_THREADS;
if(self->rc != APR_SUCCESS) {
PyErr_SetString(PyExc_IOError, "Flush failed.");
return NULL;
}
}
Py_INCREF(Py_None);
return Py_None;
}
/**
** filter.close(filter self)
**
* passes EOS
*/
static PyObject *filter_close(filterobject *self, PyObject *args)
{
conn_rec *c = self->request_obj->request_rec->connection;
if (! self->closed) {
/* does the output brigade exist? */
if (!self->bb_out) {
self->bb_out = apr_brigade_create(self->f->r->pool,
c->bucket_alloc);
}
APR_BRIGADE_INSERT_TAIL(self->bb_out,
apr_bucket_eos_create(c->bucket_alloc));
if (! self->is_input) {
Py_BEGIN_ALLOW_THREADS;
self->rc = ap_pass_brigade(self->f->next, self->bb_out);
apr_brigade_destroy(self->bb_out);
Py_END_ALLOW_THREADS;
self->bb_out = NULL;
}
self->closed = 1;
}
Py_INCREF(Py_None);
return Py_None;
}
/**
** filter.disable(filter self)
**
* Sets the transparent flag on causeing the filter_handler to
* just pass the data through without envoking Python at all.
* This is used during filter error output.
*/
static PyObject *filter_disable(filterobject *self, PyObject *args)
{
python_filter_ctx *ctx;
ctx = (python_filter_ctx *) self->f->ctx;
ctx->transparent = 1;
Py_INCREF(Py_None);
return Py_None;
}
static PyMethodDef filterobjectmethods[] = {
{"pass_on", (PyCFunction) filter_pass_on, METH_NOARGS},
{"read", (PyCFunction) filter_read, METH_VARARGS},
{"readline", (PyCFunction) filter_readline, METH_VARARGS},
{"write", (PyCFunction) filter_write, METH_VARARGS},
{"flush", (PyCFunction) filter_flush, METH_VARARGS},
{"close", (PyCFunction) filter_close, METH_VARARGS},
{"disable", (PyCFunction) filter_disable, METH_VARARGS},
{NULL, NULL}
};
#define OFF(x) offsetof(filterobject, x)
static PyMemberDef filter_memberlist[] = {
{"softspace", T_INT, OFF(softspace), },
{"closed", T_INT, OFF(closed), READONLY},
{"name", T_OBJECT, 0, READONLY},
{"req", T_OBJECT, OFF(request_obj), },
{"is_input", T_INT, OFF(is_input), READONLY},
{"handler", T_STRING, OFF(handler), READONLY},
{"dir", T_STRING, OFF(dir), READONLY},
{NULL} /* Sentinel */
};
/**
** filter_dealloc
**
*
*/
static void filter_dealloc(filterobject *self)
{
Py_XDECREF(self->request_obj);
PyObject_Del(self);
}
/**
** filter_getattr
**
* Get filter object attributes
*
*
*/
static PyObject * filter_getattr(filterobject *self, char *name)
{
PyObject *res;
PyMethodDef *ml = filterobjectmethods;
for (; ml->ml_name != NULL; ml++) {
if (name[0] == ml->ml_name[0] &&
strcmp(name+1, ml->ml_name+1) == 0)
return PyCFunction_New(ml, (PyObject*)self);
}
PyErr_Clear();
if (strcmp(name, "name") == 0) {
if (! self->f->frec->name) {
Py_INCREF(Py_None);
return Py_None;
}
else {
return MpBytesOrUnicode_FromString(self->f->frec->name);
}
}
else if (strcmp(name, "req") == 0) {
if (! self->request_obj) {
Py_INCREF(Py_None);
return Py_None;
}
else {
Py_INCREF(self->request_obj);
return (PyObject *)self->request_obj;
}
}
else {
PyMemberDef *md = find_memberdef(filter_memberlist, name);
if (!md) {
PyErr_SetString(PyExc_AttributeError, name);
return NULL;
}
return PyMember_GetOne((char*)self, md);
}
}
/**
** filter_setattr
**
* Set filter object attributes
*
*/
static int filter_setattr(filterobject *self, char *name, PyObject *v)
{
if (v == NULL) {
PyErr_SetString(PyExc_AttributeError,
"can't delete filter attributes");
return -1;
}
PyMemberDef *md = find_memberdef(filter_memberlist, name);
if (!md) {
PyErr_SetString(PyExc_AttributeError, name);
return -1;
}
return PyMember_SetOne((char*)self, md, v);
}
PyTypeObject MpFilter_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0)
"mp_filter", /* tp_name */
sizeof(filterobject), /* tp_basicsize */
0, /* tp_itemsize */
(destructor) filter_dealloc, /* tp_dealloc*/
0, /* tp_print*/
(getattrfunc) filter_getattr, /* tp_getattr*/
(setattrfunc) filter_setattr, /* tp_setattr*/
0, /* tp_compare*/
0, /* tp_repr*/
0, /* tp_as_number*/
0, /* tp_as_sequence*/
0, /* tp_as_mapping*/
0, /* tp_hash*/
};