Hi mod_python-dev,

I have created a patch for mod_python that fixes up the following issues:

 MODPYTHON-212
req.read() with no arguments doesn't return all data where input filter inserts extra data in input stream

 MODPYTHON-222
 Support for chunked transfer encoding on request content

It does this by:

(1) Making read() and readline() ignore the content length in the case where no size argument is specified, and instead call ap_get_client_block() in a loop until it returns zero. The result buffer is dynamically expanded as necessary with _PyString_Resize().

(2) Adding a request.set_read_policy() call to specify whether REQUEST_NO_BODY, REQUEST_CHUNKED_ERROR or REQUEST_CHUNKED_DECHUNK is passed to ap_setup_client_block() on the first read()/readline() call. The default is REQUEST_CHUNKED_DECHUNK, but could be set to REQUEST_CHUNKED_ERROR for compatibility with the current behaviour.

This patch is against the mod_python-3.3.1 release. Please find it attached as "modpython-212-222-fix.patch". I have tested it manually and run test.py on it as well.


--- Possible Objections ---

Aside from content-length-related objections that are obviously addressed by the above, I also noted the following objection to enabling the use of REQUEST_CHUNKED_DECHUNK on the mailing list:

Graham Dumpleton, 2007-02-23,
http://www.webservertalk.com/archive309-2007-2-1820928.html

> [...] when using REQUEST_CHUNKED_DECHUNK it is important
> that the read buffer be large enough to handle any chunk being read. Ie.,
> from Apache source code:
>
> [...]
> * In order to use the last two options, the caller MUST provide a buffer
> * large enough to hold a chunk-size line, including any extensions.
>
> If chunked is used the mod_python source can't know what the buffer size
> it would need to use is as that is going to be dependent on the higher level
> application and what it is doing.

If you look carefully here, for example,

http://mail-archives.apache.org/mod_mbox/httpd-cvs/199611.mbox/
[EMAIL PROTECTED]

you can see old get_client_block() code that reads the chunk header line into the client-provided buffer before applying chunk decoding, hence the requirement that "the caller MUST provide a buffer large enough to hold a chunk-size line, including any extensions". However, the current ap_get_client_block() has completely replaced this with some APR "bucket brigade" magic. It seems to me that they just forgot to remove this comment.


--- Substitute Patch ---

If this patch is not satisfactory, I would suggest at least the changes in the attached patch "requestobject.c.fix1.patch". This corrects the following problems in req_read():

(1) The first ap_get_client_block() call will currently overwrite any data retrieved by the preceding "If anything left in the readline buffer" code.

(2) The return value of the first call to ap_get_client_block() is not checked for errors (though it does look like it will try again in the loop below, where error checking is in fact done).

An additional problem NOT corrected by "requestobject.c.fix1.patch" (but corrected by the main patch):

(3) It seems that req_readline() allocates a buffer the size of the whole response just to return one line if the size argument is unspecified.


--- Mailing List Problems ---

By the way, it looks like the mod_python web page needs to be updated regarding the address of the -dev email list. I had to go through the following redirects to actually find the list:

"<[EMAIL PROTECTED]>: This mailing list has moved to python-dev at quetz.apache.org."

"<[EMAIL PROTECTED]>: This mailing list has moved to mod_python-dev at quetz.apache.org."


Regards,
Alex

--- requestobject.c.orig        2006-12-03 15:06:37.000000000 +1030
+++ requestobject.c.fix1        2008-07-10 22:38:09.015625000 +0930
@@ -847,10 +847,11 @@
 
 static PyObject * req_read(requestobject *self, PyObject *args)
 {
-    int rc, bytes_read, chunk_len;
+    int rc;
+    long bytes_read, chunk_len;
     char *buffer;
     PyObject *result;
-    int copied = 0;
+    long copied = 0;
     long len = -1;
 
     if (! PyArg_ParseTuple(args, "|l", &len)) 
@@ -907,13 +908,9 @@
     if (copied == len)
         return result;  /* we're done! */
 
-    /* read it in */
-    Py_BEGIN_ALLOW_THREADS
-    chunk_len = ap_get_client_block(self->request_rec, buffer, len);
-    Py_END_ALLOW_THREADS
-    bytes_read = chunk_len;
-
-    /* if this is a "short read", try reading more */
+    /* read it in, looping for short reads */
+    bytes_read = copied;
+    chunk_len = 1;
     while ((bytes_read < len) && (chunk_len != 0)) {
         Py_BEGIN_ALLOW_THREADS
         chunk_len = ap_get_client_block(self->request_rec, 

diff -ur mod_python-3.3.1/lib/python/mod_python/apache.py 
mod_python-3.3.1.p/lib/python/mod_python/apache.py
--- mod_python-3.3.1/lib/python/mod_python/apache.py    2006-10-27 
10:24:12.000000000 +0930
+++ mod_python-3.3.1.p/lib/python/mod_python/apache.py  2008-07-10 
22:54:19.000000000 +0930
@@ -1153,10 +1153,10 @@
 INCLUDES_MAGIC_TYPE3 = "text/x-server-parsed-html3" 
 DIR_MAGIC_TYPE = "httpd/unix-directory" 
 
-# for req.read_body
-REQUEST_NO_BODY = 0 
-REQUEST_CHUNKED_ERROR = 1 
-REQUEST_CHUNKED_DECHUNK = 2 
+# for req.read_body and req.set_read_policy()
+REQUEST_NO_BODY = _apache.REQUEST_NO_BODY
+REQUEST_CHUNKED_ERROR = _apache.REQUEST_CHUNKED_ERROR
+REQUEST_CHUNKED_DECHUNK = _apache.REQUEST_CHUNKED_DECHUNK
 
 # for req.connection.keepalive
 AP_CONN_UNKNOWN = _apache.AP_CONN_UNKNOWN
diff -ur mod_python-3.3.1/src/_apachemodule.c 
mod_python-3.3.1.p/src/_apachemodule.c
--- mod_python-3.3.1/src/_apachemodule.c        2006-11-23 10:38:36.000000000 
+1030
+++ mod_python-3.3.1.p/src/_apachemodule.c      2008-07-10 22:43:43.000000000 
+0930
@@ -780,6 +780,16 @@
 
     PyDict_SetItemString(d, "table", (PyObject *)&MpTable_Type);
 
+    o = PyInt_FromLong(REQUEST_NO_BODY);
+    PyDict_SetItemString(d, "REQUEST_NO_BODY", o);
+    Py_DECREF(o);
+    o = PyInt_FromLong(REQUEST_CHUNKED_ERROR);
+    PyDict_SetItemString(d, "REQUEST_CHUNKED_ERROR", o);
+    Py_DECREF(o);
+    o = PyInt_FromLong(REQUEST_CHUNKED_DECHUNK);
+    PyDict_SetItemString(d, "REQUEST_CHUNKED_DECHUNK", o);
+    Py_DECREF(o);
+
     o = PyInt_FromLong(AP_CONN_UNKNOWN);
     PyDict_SetItemString(d, "AP_CONN_UNKNOWN", o);
     Py_DECREF(o);
diff -ur mod_python-3.3.1/src/include/mod_python.h 
mod_python-3.3.1.p/src/include/mod_python.h
--- mod_python-3.3.1/src/include/mod_python.h   2006-10-24 13:11:54.000000000 
+0930
+++ mod_python-3.3.1.p/src/include/mod_python.h 2008-07-11 00:28:59.000000000 
+0930
@@ -21,7 +21,7 @@
  *
  * mod_python.h 
  *
- * $Id: mod_python.h 467228 2006-10-24 03:41:54Z grahamd $
+ * $Id: mod_python.h 231054 2005-08-09 15:37:04Z jgallacher $
  *
  * See accompanying documentation and source code comments 
  * for details.
diff -ur mod_python-3.3.1/src/include/requestobject.h 
mod_python-3.3.1.p/src/include/requestobject.h
--- mod_python-3.3.1/src/include/requestobject.h        2006-10-08 
19:28:35.000000000 +0930
+++ mod_python-3.3.1.p/src/include/requestobject.h      2008-07-10 
22:56:38.000000000 +0930
@@ -49,6 +49,7 @@
         int              rbuff_len;   /* read buffer size */
         int              rbuff_pos;   /* position into the buffer */
         PyObject       * session;
+        int              read_policy;
        
     } requestobject;
 
diff -ur mod_python-3.3.1/src/requestobject.c 
mod_python-3.3.1.p/src/requestobject.c
--- mod_python-3.3.1/src/requestobject.c        2006-12-03 15:06:37.000000000 
+1030
+++ mod_python-3.3.1.p/src/requestobject.c      2008-07-11 01:01:40.000000000 
+0930
@@ -24,6 +24,8 @@
 
 #include "mod_python.h"
 
+#define ALLOCATED_RBUFF_SIZE HUGE_STRING_LEN
+
 /* mod_ssl.h is not safe for inclusion in 2.0, so duplicate the
  * optional function declarations. */
 APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
@@ -76,6 +78,7 @@
     result->rbuff = NULL;
     result->rbuff_pos = 0;
     result->rbuff_len = 0;
+    result->read_policy = REQUEST_CHUNKED_DECHUNK;
 
     /* we make sure that the object dictionary is there
      * before registering the object with the GC
@@ -837,83 +840,83 @@
     return PyInt_FromLong(ap_meets_conditions(self->request_rec));
 }
 
-
 /**
- ** request.read(request self, int bytes)
+ ** request.set_read_policy(request self, int read_policy)
  **
- *     Reads stuff like POST requests from the client
- *     (based on the old net_read)
+ *     Set the read_policy value to pass to ap_setup_client_block() on the
+ *     first read()/readline().
+ *
+ *     Valid values include apache.REQUEST_NO_BODY,
+ *     apache.REQUEST_CHUNKED_ERROR, and apache.REQUEST_CHUNKED_DECHUNK.
+ *
+ *     The default value is apache.REQUEST_CHUNKED_DECHUNK.
  */
 
-static PyObject * req_read(requestobject *self, PyObject *args)
+static PyObject * req_set_read_policy(requestobject *self, PyObject *args)
 {
-    int rc, bytes_read, chunk_len;
-    char *buffer;
-    PyObject *result;
-    int copied = 0;
-    long len = -1;
-
-    if (! PyArg_ParseTuple(args, "|l", &len)) 
+    if (! PyArg_ParseTuple(args, "i", &self->read_policy)) 
         return NULL;
 
-    if (len == 0) {
-        return PyString_FromString("");
-    }
+    Py_INCREF(Py_None);
+    return Py_None;
+}
 
-    /* is this the first read? */
-    if (! self->request_rec->read_length) {
+/* 
+ * called by req_read() and req_readline().
+ * returns false if result should be returned or true if the caller can 
continue.
+ */
+static int req_read_setup(requestobject *self, PyObject **result)
+{
+    int rc;
 
-        /* then do some initial setting up */
-        rc = ap_setup_client_block(self->request_rec, REQUEST_CHUNKED_ERROR);
-        if(rc != OK) {
-            PyObject *val = PyInt_FromLong(rc);
-            if (val == NULL)
-                return NULL;
-            PyErr_SetObject(get_ServerReturn(), val);
-            Py_DECREF(val);
-            return NULL;
-        }
+    /* do initial setting up */
+    rc = ap_setup_client_block(self->request_rec, self->read_policy);
 
-        if (! ap_should_client_block(self->request_rec)) {
-            /* client has nothing to send */
-            return PyString_FromString("");
-        }
+    if (rc != OK) {
+        *result = NULL;
+        PyObject *val = PyInt_FromLong(rc);
+        if (val == NULL)
+            return 0;
+        PyErr_SetObject(get_ServerReturn(), val);
+        Py_DECREF(val);
+        return 0;
     }
 
-    if (len < 0)
-        /* XXX ok to use request_rec->remaining? */
-        len = self->request_rec->remaining +
-            (self->rbuff_len - self->rbuff_pos);
+    if (! ap_should_client_block(self->request_rec)) {
+        /* client has nothing to send */
+        *result = PyString_FromString("");
+        return 0;
+    }
+
+    return 1;
+}
+
+/* called by req_read() to implement the len >= 0 case */
+static PyObject * req_read_some(requestobject *self, long len)
+{
+    long bytes_read = 0;
+    long chunk_len;
+    char *buffer;
+    PyObject *result;
 
     /* PYTHON 2.5: 'PyString_FromStringAndSize' uses Py_ssize_t for input 
parameters */
     result = PyString_FromStringAndSize(NULL, len);
-
-    /* possibly no more memory */
-    if (result == NULL) 
+    if (result == NULL)             /* possibly no more memory */
         return NULL;
-
     buffer = PyString_AS_STRING((PyStringObject *) result);
 
     /* if anything left in the readline buffer */
-    while ((self->rbuff_pos < self->rbuff_len) && (copied < len))
-        buffer[copied++] = self->rbuff[self->rbuff_pos++];
+    while ((self->rbuff_pos < self->rbuff_len) && (bytes_read < len))
+        buffer[bytes_read++] = self->rbuff[self->rbuff_pos++];
     
-    /* Free rbuff if we're done with it */
+    /* free rbuff if we're done with it */
     if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL) {
         free(self->rbuff);
         self->rbuff = NULL;
     }
 
-    if (copied == len)
-        return result;  /* we're done! */
-
-    /* read it in */
-    Py_BEGIN_ALLOW_THREADS
-    chunk_len = ap_get_client_block(self->request_rec, buffer, len);
-    Py_END_ALLOW_THREADS
-    bytes_read = chunk_len;
-
-    /* if this is a "short read", try reading more */
+    /* read it in, looping for short reads */
+    chunk_len = 1;
     while ((bytes_read < len) && (chunk_len != 0)) {
         Py_BEGIN_ALLOW_THREADS
         chunk_len = ap_get_client_block(self->request_rec, 
@@ -937,20 +940,84 @@
     return result;
 }
 
+/* called by req_read() to implement the len < 0 case */
+static PyObject * req_read_all(requestobject *self)
+{
+    long bytes_read = 0;
+    long chunk_len;
+    char *buffer;
+    long buffer_size = 4096;
+    PyObject *result;
+
+    /* PYTHON 2.5: 'PyString_FromStringAndSize' uses Py_ssize_t for input 
parameters */
+    result = PyString_FromStringAndSize(NULL, buffer_size);
+    if (result == NULL)             /* possibly no more memory */
+        return NULL;
+    buffer = PyString_AS_STRING((PyStringObject *) result);
+
+    /* if anything left in the readline buffer */
+    while (self->rbuff_pos < self->rbuff_len) {
+
+        /* resize buffer if full */
+        if (bytes_read == buffer_size) {
+            buffer_size *= 2;
+            if (_PyString_Resize(&result, buffer_size))
+                return NULL;
+            buffer = PyString_AS_STRING((PyStringObject *) result);
+        }
+
+        /* copy byte */
+        buffer[bytes_read++] = self->rbuff[self->rbuff_pos++];
+    }
+    
+    /* free rbuff if we're done with it */
+    if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL) {
+        free(self->rbuff);
+        self->rbuff = NULL;
+    }
+
+    /* read it in, looping for short reads */
+    do {
+        /* resize buffer if full */
+        if (bytes_read == buffer_size) {
+            buffer_size *= 2;
+            if (_PyString_Resize(&result, buffer_size))
+                return NULL;
+            buffer = PyString_AS_STRING((PyStringObject *) result);
+        }
+
+        Py_BEGIN_ALLOW_THREADS
+        chunk_len = ap_get_client_block(self->request_rec, 
+                                        buffer+bytes_read, 
buffer_size-bytes_read);
+        Py_END_ALLOW_THREADS
+        if (chunk_len == -1) {
+            PyErr_SetObject(PyExc_IOError, 
+                            PyString_FromString("Client read error 
(Timeout?)"));
+            return NULL;
+        }
+        else
+            bytes_read += chunk_len;
+    }
+    while (chunk_len != 0);
+
+    /* resize buffer to actual data size */
+    /* PYTHON 2.5: '_PyString_Resize' uses Py_ssize_t for input parameters */
+    if (_PyString_Resize(&result, bytes_read))
+        return NULL;
+
+    return result;
+}
+
 /**
- ** request.readline(request self, int maxbytes)
+ ** request.read(request self, int bytes)
  **
  *     Reads stuff like POST requests from the client
- *     (based on the old net_read) until EOL
+ *     (based on the old net_read)
  */
 
-static PyObject * req_readline(requestobject *self, PyObject *args)
+static PyObject * req_read(requestobject *self, PyObject *args)
 {
-
-    int rc, chunk_len, bytes_read;
-    char *buffer;
     PyObject *result;
-    int copied = 0;
     long len = -1;
 
     if (! PyArg_ParseTuple(args, "|l", &len)) 
@@ -960,122 +1027,113 @@
         return PyString_FromString("");
     }
 
-    /* is this the first read? */
+    /* do first-read setup if necessary */
     if (! self->request_rec->read_length) {
+        if (! req_read_setup(self, &result))
+            return result;
+    }
 
-        /* then do some initial setting up */
-        rc = ap_setup_client_block(self->request_rec, REQUEST_CHUNKED_ERROR);
+    if (len < 0)
+        return req_read_all(self);
+    else
+        return req_read_some(self, len);
+}
 
-        if (rc != OK) {
-            PyObject *val = PyInt_FromLong(rc);
-            if (val == NULL)
-                return NULL;
-            PyErr_SetObject(get_ServerReturn(), val);
-            Py_DECREF(val);
-            return NULL;
-        }
+/**
+ ** request.readline(request self, int maxbytes)
+ **
+ *     Reads stuff like POST requests from the client
+ *     (based on the old net_read) until EOL
+ */
 
-        if (! ap_should_client_block(self->request_rec)) {
-            /* client has nothing to send */
-            return PyString_FromString("");
-        }
+static PyObject * req_readline(requestobject *self, PyObject *args)
+{
+    PyObject *result;
+    char *buffer;
+    long buffer_size;
+    long copied = 0;
+    long len = -1;
+
+    if (! PyArg_ParseTuple(args, "|l", &len)) 
+        return NULL;
+
+    if (len == 0) {
+        return PyString_FromString("");
     }
 
-    if (len < 0)
-        len = self->request_rec->remaining + 
-            (self->rbuff_len - self->rbuff_pos);
+    /* do first-read setup if necessary */
+    if (! self->request_rec->read_length) {
+        if (! req_read_setup(self, &result))
+            return result;
+    }
 
     /* create the result buffer */
     /* PYTHON 2.5: 'PyString_FromStringAndSize' uses Py_ssize_t for input 
parameters */
-    result = PyString_FromStringAndSize(NULL, len);
-
-    /* possibly no more memory */
-    if (result == NULL) 
+    buffer_size = (len >= 0? len : 4096);
+    result = PyString_FromStringAndSize(NULL, buffer_size);
+    if (result == NULL)             /* possibly no more memory */
         return NULL;
-
     buffer = PyString_AS_STRING((PyStringObject *) result);
 
-    /* is there anything left in the rbuff from previous reads? */
-    if (self->rbuff_pos < self->rbuff_len) {
-        
-        /* if yes, process that first */
-        while (self->rbuff_pos < self->rbuff_len) {
+    /* process anything left in the rbuff from previous reads */
+    while (self->rbuff_pos < self->rbuff_len) {
 
-            buffer[copied++] = self->rbuff[self->rbuff_pos];
-            if ((self->rbuff[self->rbuff_pos++] == '\n') || 
-                (copied == len)) {
+        /* resize result buffer if full */
+        if (copied == buffer_size) {
+            buffer_size *= 2;
+            if (_PyString_Resize(&result, buffer_size))
+                return NULL;
+            buffer = PyString_AS_STRING((PyStringObject *) result);
+        }
 
-                /* our work is done */
+        /* copy byte */
+        buffer[copied++] = self->rbuff[self->rbuff_pos];
+        if ((self->rbuff[self->rbuff_pos++] == '\n') || 
+            (copied == len)) {          /* note len may be < 0 */
 
-                /* resize if necessary */
-                if (copied < len) 
-                    /* PYTHON 2.5: '_PyString_Resize' uses Py_ssize_t for 
input parameters */
-                    if(_PyString_Resize(&result, copied))
-                        return NULL;
-
-                /* fix for MODPYTHON-181 leak */
-                if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL) 
{
-                    free(self->rbuff);
-                    self->rbuff = NULL;
-                } 
+            /* our work is done */
 
-                return result;
-            }
-        }
-    }
+            /* resize result buffer if necessary */
+            if (copied < buffer_size) 
+                /* PYTHON 2.5: '_PyString_Resize' uses Py_ssize_t for input 
parameters */
+                if(_PyString_Resize(&result, copied))
+                    return NULL;
 
-    /* Free old rbuff as the old contents have been copied over and
-       we are about to allocate a new rbuff. Perhaps this could be reused
-       somehow? */
-    if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL)
-    {
-        free(self->rbuff);
-        self->rbuff = NULL;
+            /* fix for MODPYTHON-181 leak */
+            if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL) {
+                free(self->rbuff);
+                self->rbuff = NULL;
+            } 
+
+            return result;
+        }
     }
 
-    /* if got this far, the buffer should be empty, we need to read more */
+    /* if got this far, the rbuff should be empty, we need to read more */
         
-    /* create a read buffer
+    /* create a read buffer if not already present
       
-       The buffer len will be at least HUGE_STRING_LEN in size,
+       The buffer len will always be exactly HUGE_STRING_LEN in size,
        to avoid memory fragmention
     */
-    self->rbuff_len = len > HUGE_STRING_LEN ? len : HUGE_STRING_LEN;
-    self->rbuff_pos = 0;
-    self->rbuff = malloc(self->rbuff_len);
-    if (! self->rbuff)
-        return PyErr_NoMemory();
-
-    /* read it in */
-    Py_BEGIN_ALLOW_THREADS
-        chunk_len = ap_get_client_block(self->request_rec, self->rbuff, 
-                                        self->rbuff_len);
-    Py_END_ALLOW_THREADS;
-
-    /* ap_get_client_block could return -1 on error */
-    if (chunk_len == -1) {
-
-        /* Free rbuff since returning NULL here should end the request */
-        free(self->rbuff);
-        self->rbuff = NULL;
-
-        PyErr_SetObject(PyExc_IOError, 
-                        PyString_FromString("Client read error (Timeout?)"));
-        return NULL;
+    if (! self->rbuff) {
+        self->rbuff = malloc(ALLOCATED_RBUFF_SIZE);
+        if (! self->rbuff)
+            return PyErr_NoMemory();
     }
-    
-    bytes_read = chunk_len;
 
-    /* if this is a "short read", try reading more */
-    while ((chunk_len != 0 ) && (bytes_read + copied < len)) {
+    for (;;) {
 
+        /* read some data into rbuff */
         Py_BEGIN_ALLOW_THREADS
-            chunk_len = ap_get_client_block(self->request_rec, 
-                                            self->rbuff + bytes_read, 
-                                            self->rbuff_len - bytes_read);
+            self->rbuff_pos = 0;
+            self->rbuff_len = (int)ap_get_client_block(self->request_rec, 
+                                                       self->rbuff, 
+                                                       ALLOCATED_RBUFF_SIZE);
         Py_END_ALLOW_THREADS
 
-        if (chunk_len == -1) {
+        /* check read result */
+        if (self->rbuff_len == -1) {
 
             /* Free rbuff since returning NULL here should end the request */
             free(self->rbuff);
@@ -1085,31 +1143,48 @@
                             PyString_FromString("Client read error 
(Timeout?)"));
             return NULL;
         }
-        else
-            bytes_read += chunk_len;
-    }
-    self->rbuff_len = bytes_read;
-    self->rbuff_pos = 0;
 
-    /* now copy the remaining bytes */
-    while (self->rbuff_pos < self->rbuff_len) {
-
-        buffer[copied++] = self->rbuff[self->rbuff_pos];
-        if ((self->rbuff[self->rbuff_pos++] == '\n') || 
-            (copied == len)) 
-            /* our work is done */
+        /* end of stream? */
+        if (self->rbuff_len == 0)
             break;
+
+        /* copy into result buffer up to \n or specified length */
+        while (self->rbuff_pos < self->rbuff_len) {
+
+            /* resize buffer if full */
+            if (copied == buffer_size) {
+                buffer_size *= 2;
+                if (_PyString_Resize(&result, buffer_size))
+                    return NULL;
+                buffer = PyString_AS_STRING((PyStringObject *) result);
+            }
+
+            /* copy byte */
+            buffer[copied++] = self->rbuff[self->rbuff_pos];
+            if ((self->rbuff[self->rbuff_pos++] == '\n') || 
+                (copied == len)) {      /* len may be < 0 meaning this test 
will always pass */
+
+                /* leave rbuff_pos and rbuff_len describing the remaining data 
*/
+
+                /* our work is done */
+                goto break_outer_loop;
+            }
+        }
+        
+        /* reset */
+        self->rbuff_pos = 0;
     }
 
+break_outer_loop:
     /* Free rbuff if we're done with it */
-    if (self->rbuff_pos >= self->rbuff_len && self->rbuff != NULL)
+    if (self->rbuff_pos >= self->rbuff_len)
     {
         free(self->rbuff);
         self->rbuff = NULL;
     }
 
-    /* resize if necessary */
-    if (copied < len) 
+    /* resize result buffer if necessary */
+    if (copied < buffer_size) 
         /* PYTHON 2.5: '_PyString_Resize' uses Py_ssize_t for input parameters 
*/
         if(_PyString_Resize(&result, copied))
             return NULL;
@@ -1472,6 +1547,7 @@
     {"is_https",              (PyCFunction) req_is_https,              
METH_NOARGS},
     {"log_error",             (PyCFunction) req_log_error,             
METH_VARARGS},
     {"meets_conditions",      (PyCFunction) req_meets_conditions,      
METH_NOARGS},
+    {"set_read_policy",       (PyCFunction) req_set_read_policy,       
METH_VARARGS},
     {"read",                  (PyCFunction) req_read,                  
METH_VARARGS},
     {"readline",              (PyCFunction) req_readline,              
METH_VARARGS},
     {"readlines",             (PyCFunction) req_readlines,             
METH_VARARGS},
Only in mod_python-3.3.1.p/test: modules

Reply via email to