]> granicus.if.org Git - apache/blobdiff - modules/http2/h2_stream.c
On the 2.4.x branch:
[apache] / modules / http2 / h2_stream.c
index 7bf35aa3b27247ef2c32a098b0cab5e312a66459..b2703de7120295f575cf56b5ed74c4ada3e1261d 100644 (file)
@@ -1,18 +1,19 @@
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
  *
- * 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
  *
- * 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.
  */
-
 #include <assert.h>
 #include <stddef.h>
 
@@ -444,7 +445,13 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags)
             else {
                 /* request HEADER */
                 ap_assert(stream->request == NULL);
-                ap_assert(stream->rtmp != NULL);
+                if (stream->rtmp == NULL) {
+                    /* This can only happen, if the stream has received no header
+                     * name/value pairs at all. The lastest nghttp2 version have become
+                     * pretty good at detecting this early. In any case, we have
+                     * to abort the connection here, since this is clearly a protocol error */
+                    return APR_EINVAL;
+                }
                 status = h2_request_end_headers(stream->rtmp, stream->pool, eos);
                 if (status != APR_SUCCESS) {
                     return status;
@@ -571,17 +578,7 @@ void h2_stream_destroy(h2_stream *stream)
     ap_assert(stream);
     ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c, 
                   H2_STRM_MSG(stream, "destroy"));
-    if (stream->pool) {
-        apr_pool_destroy(stream->pool);
-        stream->pool = NULL;
-    }
-}
-
-apr_pool_t *h2_stream_detach_pool(h2_stream *stream)
-{
-    apr_pool_t *pool = stream->pool;
-    stream->pool = NULL;
-    return pool;
+    apr_pool_destroy(stream->pool);
 }
 
 apr_status_t h2_stream_prep_processing(h2_stream *stream)
@@ -739,9 +736,13 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
         status = h2_request_add_header(stream->rtmp, stream->pool,
                                        name, nlen, value, vlen);
     }
-    else  {
+    else if (H2_SS_OPEN == stream->state) {
         status = add_trailer(stream, name, nlen, value, vlen);
     }
+    else {
+        status = APR_EINVAL;
+    }
+    
     if (status != APR_SUCCESS) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
                       H2_STRM_MSG(stream, "header %s not accepted"), name);
@@ -764,18 +765,77 @@ static apr_bucket *get_first_headers_bucket(apr_bucket_brigade *bb)
     return NULL;
 }
 
+static apr_status_t add_buffered_data(h2_stream *stream, apr_off_t requested,
+                                      apr_off_t *plen, int *peos, int *is_all, 
+                                      h2_headers **pheaders)
+{
+    apr_bucket *b, *e;
+    
+    *peos = 0;
+    *plen = 0;
+    *is_all = 0;
+    if (pheaders) {
+        *pheaders = NULL;
+    }
+
+    H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "add_buffered_data");
+    b = APR_BRIGADE_FIRST(stream->out_buffer);
+    while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+        e = APR_BUCKET_NEXT(b);
+        if (APR_BUCKET_IS_METADATA(b)) {
+            if (APR_BUCKET_IS_FLUSH(b)) {
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+            }
+            else if (APR_BUCKET_IS_EOS(b)) {
+                *peos = 1;
+                return APR_SUCCESS;
+            }
+            else if (H2_BUCKET_IS_HEADERS(b)) {
+                if (*plen > 0) {
+                    /* data before the response, can only return up to here */
+                    return APR_SUCCESS;
+                }
+                else if (pheaders) {
+                    *pheaders = h2_bucket_headers_get(b);
+                    APR_BUCKET_REMOVE(b);
+                    apr_bucket_destroy(b);
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+                                  H2_STRM_MSG(stream, "prep, -> response %d"), 
+                                  (*pheaders)->status);
+                    return APR_SUCCESS;
+                }
+                else {
+                    return APR_EAGAIN;
+                }
+            }
+        }
+        else if (b->length == 0) {
+            APR_BUCKET_REMOVE(b);
+            apr_bucket_destroy(b);
+        }
+        else {
+            ap_assert(b->length != (apr_size_t)-1);
+            *plen += b->length;
+            if (*plen >= requested) {
+                *plen = requested;
+                return APR_SUCCESS;
+            }
+        }
+        b = e;
+    }
+    *is_all = 1;
+    return APR_SUCCESS;
+}
+
 apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen, 
-                                   int *peos, h2_headers **presponse)
+                                   int *peos, h2_headers **pheaders)
 {
     apr_status_t status = APR_SUCCESS;
-    apr_off_t requested, max_chunk = H2_DATA_CHUNK_SIZE;
-    apr_bucket *b, *e;
+    apr_off_t requested, missing, max_chunk = H2_DATA_CHUNK_SIZE;
     conn_rec *c;
+    int complete;
 
-    if (presponse) {
-        *presponse = NULL;
-    }
-    
     ap_assert(stream);
     
     if (stream->rst_error) {
@@ -793,96 +853,70 @@ apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen,
     if (stream->session->io.write_size > 0) {
         max_chunk = stream->session->io.write_size - 9; /* header bits */ 
     }
-    *plen = requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;
+    requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;
+    
+    /* count the buffered data until eos or a headers bucket */
+    status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
+    
+    if (status == APR_EAGAIN) {
+        /* TODO: ugly, someone needs to retrieve the response first */
+        h2_mplx_keep_active(stream->session->mplx, stream);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      H2_STRM_MSG(stream, "prep, response eagain"));
+        return status;
+    }
+    else if (status != APR_SUCCESS) {
+        return status;
+    }
     
-    h2_util_bb_avail(stream->out_buffer, plen, peos);
-    if (!*peos && *plen < requested && *plen < stream->max_mem) {
-        H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
+    if (pheaders && *pheaders) {
+        return APR_SUCCESS;
+    }
+    
+    /* If there we do not have enough buffered data to satisfy the requested
+     * length *and* we counted the _complete_ buffer (and did not stop in the middle
+     * because of meta data there), lets see if we can read more from the
+     * output beam */
+    missing = H2MIN(requested, stream->max_mem) - *plen;
+    if (complete && !*peos && missing > 0) {
+        apr_status_t rv = APR_EOF;
+        
         if (stream->output) {
-            status = h2_beam_receive(stream->output, stream->out_buffer, 
-                                     APR_NONBLOCK_READ, 
-                                     stream->max_mem - *plen);
-        }
-        else {
-            status = APR_EOF;
+            H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
+            rv = h2_beam_receive(stream->output, stream->out_buffer, 
+                                 APR_NONBLOCK_READ, stream->max_mem - *plen);
+            H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "post");
         }
         
-        if (APR_STATUS_IS_EOF(status)) {
+        if (rv == APR_SUCCESS) {
+            /* count the buffer again, now that we have read output */
+            status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
+        }
+        else if (APR_STATUS_IS_EOF(rv)) {
             apr_bucket *eos = apr_bucket_eos_create(c->bucket_alloc);
             APR_BRIGADE_INSERT_TAIL(stream->out_buffer, eos);
-            status = APR_SUCCESS;
+            *peos = 1;
         }
-        else if (status == APR_EAGAIN) {
-            status = APR_SUCCESS;
-        }
-        *plen = requested;
-        h2_util_bb_avail(stream->out_buffer, plen, peos);
-        H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "post");
-    }
-    else {
-        H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "ok");
-    }
-
-    b = APR_BRIGADE_FIRST(stream->out_buffer);
-    while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
-        e = APR_BUCKET_NEXT(b);
-        if (APR_BUCKET_IS_FLUSH(b)
-            || (!APR_BUCKET_IS_METADATA(b) && b->length == 0)) {
-            APR_BUCKET_REMOVE(b);
-            apr_bucket_destroy(b);
-        }
-        else {
-            break;
-        }
-        b = e;
-    }
-
-    b = get_first_headers_bucket(stream->out_buffer);
-    if (b) {
-        /* there are HEADERS to submit */
-        *peos = 0;
-        *plen = 0;
-        if (b == APR_BRIGADE_FIRST(stream->out_buffer)) {
-            if (presponse) {
-                *presponse = h2_bucket_headers_get(b);
-                APR_BUCKET_REMOVE(b);
-                apr_bucket_destroy(b);
-                status = APR_SUCCESS;
-            }
-            else {
-                /* someone needs to retrieve the response first */
-                h2_mplx_keep_active(stream->session->mplx, stream->id);
-                status = APR_EAGAIN;
-            }
+        else if (APR_STATUS_IS_EAGAIN(rv)) {
+            /* we set this is the status of this call only if there
+             * is no buffered data, see check below */
         }
         else {
-            apr_bucket *e = APR_BRIGADE_FIRST(stream->out_buffer);
-            while (e != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
-                if (e == b) {
-                    break;
-                }
-                else if (e->length != (apr_size_t)-1) {
-                    *plen += e->length;
-                }
-                e = APR_BUCKET_NEXT(e);
-            }
+            /* real error reading. Give this back directly, even though
+             * we may have something buffered. */
+            status = rv;
         }
     }
-
+    
     if (status == APR_SUCCESS) {
-        if (presponse && *presponse) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
-                          H2_STRM_MSG(stream, "prepare, response %d"), 
-                          (*presponse)->status);
-        }
-        else if (*peos || *plen) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+        if (*peos || *plen) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                           H2_STRM_MSG(stream, "prepare, len=%ld eos=%d"),
                           (long)*plen, *peos);
         }
         else {
             status = APR_EAGAIN;
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                           H2_STRM_MSG(stream, "prepare, no data"));
         }
     }
@@ -985,7 +1019,7 @@ apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount)
         apr_off_t consumed = amount;
         
         while (consumed > 0) {
-            int len = (consumed > INT_MAX)? INT_MAX : consumed;
+            int len = (consumed > INT_MAX)? INT_MAX : (int)consumed;
             nghttp2_session_consume(session->ngh2, stream->id, len);
             consumed -= len;
         }