|  |  | 
|  | /* | 
|  | * Copyright (C) Igor Sysoev | 
|  | * Copyright (C) Nginx, Inc. | 
|  | */ | 
|  |  | 
|  |  | 
|  | #include <ngx_config.h> | 
|  | #include <ngx_core.h> | 
|  | #include <ngx_http.h> | 
|  |  | 
|  |  | 
|  | static void ngx_http_read_client_request_body_handler(ngx_http_request_t *r); | 
|  | static ngx_int_t ngx_http_do_read_client_request_body(ngx_http_request_t *r); | 
|  | static ngx_int_t ngx_http_write_request_body(ngx_http_request_t *r); | 
|  | static ngx_int_t ngx_http_read_discarded_request_body(ngx_http_request_t *r); | 
|  | static ngx_int_t ngx_http_discard_request_body_filter(ngx_http_request_t *r, | 
|  | ngx_buf_t *b); | 
|  | static ngx_int_t ngx_http_test_expect(ngx_http_request_t *r); | 
|  |  | 
|  | static ngx_int_t ngx_http_request_body_filter(ngx_http_request_t *r, | 
|  | ngx_chain_t *in); | 
|  | static ngx_int_t ngx_http_request_body_length_filter(ngx_http_request_t *r, | 
|  | ngx_chain_t *in); | 
|  | static ngx_int_t ngx_http_request_body_chunked_filter(ngx_http_request_t *r, | 
|  | ngx_chain_t *in); | 
|  | static ngx_int_t ngx_http_request_body_save_filter(ngx_http_request_t *r, | 
|  | ngx_chain_t *in); | 
|  |  | 
|  |  | 
|  | ngx_int_t | 
|  | ngx_http_read_client_request_body(ngx_http_request_t *r, | 
|  | ngx_http_client_body_handler_pt post_handler) | 
|  | { | 
|  | size_t                     preread; | 
|  | ssize_t                    size; | 
|  | ngx_int_t                  rc; | 
|  | ngx_buf_t                 *b; | 
|  | ngx_chain_t                out, *cl; | 
|  | ngx_http_request_body_t   *rb; | 
|  | ngx_http_core_loc_conf_t  *clcf; | 
|  |  | 
|  | r->main->count++; | 
|  |  | 
|  | #if (NGX_HTTP_SPDY) | 
|  | if (r->spdy_stream) { | 
|  | rc = ngx_http_spdy_read_request_body(r, post_handler); | 
|  | goto done; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | if (r != r->main || r->request_body || r->discard_body) { | 
|  | post_handler(r); | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | if (ngx_http_test_expect(r) != NGX_OK) { | 
|  | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); | 
|  | if (rb == NULL) { | 
|  | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * set by ngx_pcalloc(): | 
|  | * | 
|  | *     rb->bufs = NULL; | 
|  | *     rb->buf = NULL; | 
|  | *     rb->free = NULL; | 
|  | *     rb->busy = NULL; | 
|  | *     rb->chunked = NULL; | 
|  | */ | 
|  |  | 
|  | rb->rest = -1; | 
|  | rb->post_handler = post_handler; | 
|  |  | 
|  | r->request_body = rb; | 
|  |  | 
|  | if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) { | 
|  | post_handler(r); | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | preread = r->header_in->last - r->header_in->pos; | 
|  |  | 
|  | if (preread) { | 
|  |  | 
|  | /* there is the pre-read part of the request body */ | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http client request body preread %uz", preread); | 
|  |  | 
|  | out.buf = r->header_in; | 
|  | out.next = NULL; | 
|  |  | 
|  | rc = ngx_http_request_body_filter(r, &out); | 
|  |  | 
|  | if (rc != NGX_OK) { | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | r->request_length += preread - (r->header_in->last - r->header_in->pos); | 
|  |  | 
|  | if (!r->headers_in.chunked | 
|  | && rb->rest > 0 | 
|  | && rb->rest <= (off_t) (r->header_in->end - r->header_in->last)) | 
|  | { | 
|  | /* the whole request body may be placed in r->header_in */ | 
|  |  | 
|  | b = ngx_calloc_buf(r->pool); | 
|  | if (b == NULL) { | 
|  | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | b->temporary = 1; | 
|  | b->start = r->header_in->pos; | 
|  | b->pos = r->header_in->pos; | 
|  | b->last = r->header_in->last; | 
|  | b->end = r->header_in->end; | 
|  |  | 
|  | rb->buf = b; | 
|  |  | 
|  | r->read_event_handler = ngx_http_read_client_request_body_handler; | 
|  | r->write_event_handler = ngx_http_request_empty_handler; | 
|  |  | 
|  | rc = ngx_http_do_read_client_request_body(r); | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | } else { | 
|  | /* set rb->rest */ | 
|  |  | 
|  | if (ngx_http_request_body_filter(r, NULL) != NGX_OK) { | 
|  | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | goto done; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (rb->rest == 0) { | 
|  | /* the whole request body was pre-read */ | 
|  |  | 
|  | if (r->request_body_in_file_only) { | 
|  | if (ngx_http_write_request_body(r) != NGX_OK) { | 
|  | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | cl = ngx_chain_get_free_buf(r->pool, &rb->free); | 
|  | if (cl == NULL) { | 
|  | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | b = cl->buf; | 
|  |  | 
|  | ngx_memzero(b, sizeof(ngx_buf_t)); | 
|  |  | 
|  | b->in_file = 1; | 
|  | b->file_last = rb->temp_file->file.offset; | 
|  | b->file = &rb->temp_file->file; | 
|  |  | 
|  | rb->bufs = cl; | 
|  | } | 
|  |  | 
|  | post_handler(r); | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | if (rb->rest < 0) { | 
|  | ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, | 
|  | "negative request body rest"); | 
|  | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); | 
|  |  | 
|  | size = clcf->client_body_buffer_size; | 
|  | size += size >> 2; | 
|  |  | 
|  | /* TODO: honor r->request_body_in_single_buf */ | 
|  |  | 
|  | if (!r->headers_in.chunked && rb->rest < size) { | 
|  | size = (ssize_t) rb->rest; | 
|  |  | 
|  | if (r->request_body_in_single_buf) { | 
|  | size += preread; | 
|  | } | 
|  |  | 
|  | } else { | 
|  | size = clcf->client_body_buffer_size; | 
|  | } | 
|  |  | 
|  | rb->buf = ngx_create_temp_buf(r->pool, size); | 
|  | if (rb->buf == NULL) { | 
|  | rc = NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | goto done; | 
|  | } | 
|  |  | 
|  | r->read_event_handler = ngx_http_read_client_request_body_handler; | 
|  | r->write_event_handler = ngx_http_request_empty_handler; | 
|  |  | 
|  | rc = ngx_http_do_read_client_request_body(r); | 
|  |  | 
|  | done: | 
|  |  | 
|  | if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { | 
|  | r->main->count--; | 
|  | } | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | ngx_http_read_client_request_body_handler(ngx_http_request_t *r) | 
|  | { | 
|  | ngx_int_t  rc; | 
|  |  | 
|  | if (r->connection->read->timedout) { | 
|  | r->connection->timedout = 1; | 
|  | ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); | 
|  | return; | 
|  | } | 
|  |  | 
|  | rc = ngx_http_do_read_client_request_body(r); | 
|  |  | 
|  | if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { | 
|  | ngx_http_finalize_request(r, rc); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_do_read_client_request_body(ngx_http_request_t *r) | 
|  | { | 
|  | off_t                      rest; | 
|  | size_t                     size; | 
|  | ssize_t                    n; | 
|  | ngx_int_t                  rc; | 
|  | ngx_buf_t                 *b; | 
|  | ngx_chain_t               *cl, out; | 
|  | ngx_connection_t          *c; | 
|  | ngx_http_request_body_t   *rb; | 
|  | ngx_http_core_loc_conf_t  *clcf; | 
|  |  | 
|  | c = r->connection; | 
|  | rb = r->request_body; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, | 
|  | "http read client request body"); | 
|  |  | 
|  | for ( ;; ) { | 
|  | for ( ;; ) { | 
|  | if (rb->buf->last == rb->buf->end) { | 
|  |  | 
|  | /* pass buffer to request body filter chain */ | 
|  |  | 
|  | out.buf = rb->buf; | 
|  | out.next = NULL; | 
|  |  | 
|  | rc = ngx_http_request_body_filter(r, &out); | 
|  |  | 
|  | if (rc != NGX_OK) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* write to file */ | 
|  |  | 
|  | if (ngx_http_write_request_body(r) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | /* update chains */ | 
|  |  | 
|  | rc = ngx_http_request_body_filter(r, NULL); | 
|  |  | 
|  | if (rc != NGX_OK) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | if (rb->busy != NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | rb->buf->pos = rb->buf->start; | 
|  | rb->buf->last = rb->buf->start; | 
|  | } | 
|  |  | 
|  | size = rb->buf->end - rb->buf->last; | 
|  | rest = rb->rest - (rb->buf->last - rb->buf->pos); | 
|  |  | 
|  | if ((off_t) size > rest) { | 
|  | size = (size_t) rest; | 
|  | } | 
|  |  | 
|  | n = c->recv(c, rb->buf->last, size); | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, | 
|  | "http client request body recv %z", n); | 
|  |  | 
|  | if (n == NGX_AGAIN) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (n == 0) { | 
|  | ngx_log_error(NGX_LOG_INFO, c->log, 0, | 
|  | "client prematurely closed connection"); | 
|  | } | 
|  |  | 
|  | if (n == 0 || n == NGX_ERROR) { | 
|  | c->error = 1; | 
|  | return NGX_HTTP_BAD_REQUEST; | 
|  | } | 
|  |  | 
|  | rb->buf->last += n; | 
|  | r->request_length += n; | 
|  |  | 
|  | if (n == rest) { | 
|  | /* pass buffer to request body filter chain */ | 
|  |  | 
|  | out.buf = rb->buf; | 
|  | out.next = NULL; | 
|  |  | 
|  | rc = ngx_http_request_body_filter(r, &out); | 
|  |  | 
|  | if (rc != NGX_OK) { | 
|  | return rc; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (rb->rest == 0) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (rb->buf->last < rb->buf->end) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, | 
|  | "http client request body rest %O", rb->rest); | 
|  |  | 
|  | if (rb->rest == 0) { | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!c->read->ready) { | 
|  | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); | 
|  | ngx_add_timer(c->read, clcf->client_body_timeout); | 
|  |  | 
|  | if (ngx_handle_read_event(c->read, 0) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | return NGX_AGAIN; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (c->read->timer_set) { | 
|  | ngx_del_timer(c->read); | 
|  | } | 
|  |  | 
|  | if (rb->temp_file || r->request_body_in_file_only) { | 
|  |  | 
|  | /* save the last part */ | 
|  |  | 
|  | if (ngx_http_write_request_body(r) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | cl = ngx_chain_get_free_buf(r->pool, &rb->free); | 
|  | if (cl == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | b = cl->buf; | 
|  |  | 
|  | ngx_memzero(b, sizeof(ngx_buf_t)); | 
|  |  | 
|  | b->in_file = 1; | 
|  | b->file_last = rb->temp_file->file.offset; | 
|  | b->file = &rb->temp_file->file; | 
|  |  | 
|  | rb->bufs = cl; | 
|  | } | 
|  |  | 
|  | r->read_event_handler = ngx_http_block_reading; | 
|  |  | 
|  | rb->post_handler(r); | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_write_request_body(ngx_http_request_t *r) | 
|  | { | 
|  | ssize_t                    n; | 
|  | ngx_chain_t               *cl; | 
|  | ngx_temp_file_t           *tf; | 
|  | ngx_http_request_body_t   *rb; | 
|  | ngx_http_core_loc_conf_t  *clcf; | 
|  |  | 
|  | rb = r->request_body; | 
|  |  | 
|  | ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http write client request body, bufs %p", rb->bufs); | 
|  |  | 
|  | if (rb->temp_file == NULL) { | 
|  | tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); | 
|  | if (tf == NULL) { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); | 
|  |  | 
|  | tf->file.fd = NGX_INVALID_FILE; | 
|  | tf->file.log = r->connection->log; | 
|  | tf->path = clcf->client_body_temp_path; | 
|  | tf->pool = r->pool; | 
|  | tf->warn = "a client request body is buffered to a temporary file"; | 
|  | tf->log_level = r->request_body_file_log_level; | 
|  | tf->persistent = r->request_body_in_persistent_file; | 
|  | tf->clean = r->request_body_in_clean_file; | 
|  |  | 
|  | if (r->request_body_file_group_access) { | 
|  | tf->access = 0660; | 
|  | } | 
|  |  | 
|  | rb->temp_file = tf; | 
|  |  | 
|  | if (rb->bufs == NULL) { | 
|  | /* empty body with r->request_body_in_file_only */ | 
|  |  | 
|  | if (ngx_create_temp_file(&tf->file, tf->path, tf->pool, | 
|  | tf->persistent, tf->clean, tf->access) | 
|  | != NGX_OK) | 
|  | { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (rb->bufs == NULL) { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | n = ngx_write_chain_to_temp_file(rb->temp_file, rb->bufs); | 
|  |  | 
|  | /* TODO: n == 0 or not complete and level event */ | 
|  |  | 
|  | if (n == NGX_ERROR) { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | rb->temp_file->offset += n; | 
|  |  | 
|  | /* mark all buffers as written */ | 
|  |  | 
|  | for (cl = rb->bufs; cl; cl = cl->next) { | 
|  | cl->buf->pos = cl->buf->last; | 
|  | } | 
|  |  | 
|  | rb->bufs = NULL; | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | ngx_int_t | 
|  | ngx_http_discard_request_body(ngx_http_request_t *r) | 
|  | { | 
|  | ssize_t       size; | 
|  | ngx_int_t     rc; | 
|  | ngx_event_t  *rev; | 
|  |  | 
|  | #if (NGX_HTTP_SPDY) | 
|  | if (r->spdy_stream && r == r->main) { | 
|  | r->spdy_stream->skip_data = NGX_SPDY_DATA_DISCARD; | 
|  | return NGX_OK; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | if (r != r->main || r->discard_body || r->request_body) { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | if (ngx_http_test_expect(r) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | rev = r->connection->read; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body"); | 
|  |  | 
|  | if (rev->timer_set) { | 
|  | ngx_del_timer(rev); | 
|  | } | 
|  |  | 
|  | if (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked) { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | size = r->header_in->last - r->header_in->pos; | 
|  |  | 
|  | if (size || r->headers_in.chunked) { | 
|  | rc = ngx_http_discard_request_body_filter(r, r->header_in); | 
|  |  | 
|  | if (rc != NGX_OK) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | if (r->headers_in.content_length_n == 0) { | 
|  | return NGX_OK; | 
|  | } | 
|  | } | 
|  |  | 
|  | rc = ngx_http_read_discarded_request_body(r); | 
|  |  | 
|  | if (rc == NGX_OK) { | 
|  | r->lingering_close = 0; | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { | 
|  | return rc; | 
|  | } | 
|  |  | 
|  | /* rc == NGX_AGAIN */ | 
|  |  | 
|  | r->read_event_handler = ngx_http_discarded_request_body_handler; | 
|  |  | 
|  | if (ngx_handle_read_event(rev, 0) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | r->count++; | 
|  | r->discard_body = 1; | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | void | 
|  | ngx_http_discarded_request_body_handler(ngx_http_request_t *r) | 
|  | { | 
|  | ngx_int_t                  rc; | 
|  | ngx_msec_t                 timer; | 
|  | ngx_event_t               *rev; | 
|  | ngx_connection_t          *c; | 
|  | ngx_http_core_loc_conf_t  *clcf; | 
|  |  | 
|  | c = r->connection; | 
|  | rev = c->read; | 
|  |  | 
|  | if (rev->timedout) { | 
|  | c->timedout = 1; | 
|  | c->error = 1; | 
|  | ngx_http_finalize_request(r, NGX_ERROR); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (r->lingering_time) { | 
|  | timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time(); | 
|  |  | 
|  | if ((ngx_msec_int_t) timer <= 0) { | 
|  | r->discard_body = 0; | 
|  | r->lingering_close = 0; | 
|  | ngx_http_finalize_request(r, NGX_ERROR); | 
|  | return; | 
|  | } | 
|  |  | 
|  | } else { | 
|  | timer = 0; | 
|  | } | 
|  |  | 
|  | rc = ngx_http_read_discarded_request_body(r); | 
|  |  | 
|  | if (rc == NGX_OK) { | 
|  | r->discard_body = 0; | 
|  | r->lingering_close = 0; | 
|  | ngx_http_finalize_request(r, NGX_DONE); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { | 
|  | c->error = 1; | 
|  | ngx_http_finalize_request(r, NGX_ERROR); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* rc == NGX_AGAIN */ | 
|  |  | 
|  | if (ngx_handle_read_event(rev, 0) != NGX_OK) { | 
|  | c->error = 1; | 
|  | ngx_http_finalize_request(r, NGX_ERROR); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (timer) { | 
|  |  | 
|  | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); | 
|  |  | 
|  | timer *= 1000; | 
|  |  | 
|  | if (timer > clcf->lingering_timeout) { | 
|  | timer = clcf->lingering_timeout; | 
|  | } | 
|  |  | 
|  | ngx_add_timer(rev, timer); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_read_discarded_request_body(ngx_http_request_t *r) | 
|  | { | 
|  | size_t     size; | 
|  | ssize_t    n; | 
|  | ngx_int_t  rc; | 
|  | ngx_buf_t  b; | 
|  | u_char     buffer[NGX_HTTP_DISCARD_BUFFER_SIZE]; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http read discarded body"); | 
|  |  | 
|  | ngx_memzero(&b, sizeof(ngx_buf_t)); | 
|  |  | 
|  | b.temporary = 1; | 
|  |  | 
|  | for ( ;; ) { | 
|  | if (r->headers_in.content_length_n == 0) { | 
|  | r->read_event_handler = ngx_http_block_reading; | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | if (!r->connection->read->ready) { | 
|  | return NGX_AGAIN; | 
|  | } | 
|  |  | 
|  | size = (size_t) ngx_min(r->headers_in.content_length_n, | 
|  | NGX_HTTP_DISCARD_BUFFER_SIZE); | 
|  |  | 
|  | n = r->connection->recv(r->connection, buffer, size); | 
|  |  | 
|  | if (n == NGX_ERROR) { | 
|  | r->connection->error = 1; | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | if (n == NGX_AGAIN) { | 
|  | return NGX_AGAIN; | 
|  | } | 
|  |  | 
|  | if (n == 0) { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | b.pos = buffer; | 
|  | b.last = buffer + n; | 
|  |  | 
|  | rc = ngx_http_discard_request_body_filter(r, &b); | 
|  |  | 
|  | if (rc != NGX_OK) { | 
|  | return rc; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_discard_request_body_filter(ngx_http_request_t *r, ngx_buf_t *b) | 
|  | { | 
|  | size_t                    size; | 
|  | ngx_int_t                 rc; | 
|  | ngx_http_request_body_t  *rb; | 
|  |  | 
|  | if (r->headers_in.chunked) { | 
|  |  | 
|  | rb = r->request_body; | 
|  |  | 
|  | if (rb == NULL) { | 
|  |  | 
|  | rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); | 
|  | if (rb == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | rb->chunked = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_t)); | 
|  | if (rb->chunked == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | r->request_body = rb; | 
|  | } | 
|  |  | 
|  | for ( ;; ) { | 
|  |  | 
|  | rc = ngx_http_parse_chunked(r, b, rb->chunked); | 
|  |  | 
|  | if (rc == NGX_OK) { | 
|  |  | 
|  | /* a chunk has been parsed successfully */ | 
|  |  | 
|  | size = b->last - b->pos; | 
|  |  | 
|  | if ((off_t) size > rb->chunked->size) { | 
|  | b->pos += rb->chunked->size; | 
|  | rb->chunked->size = 0; | 
|  |  | 
|  | } else { | 
|  | rb->chunked->size -= size; | 
|  | b->pos = b->last; | 
|  | } | 
|  |  | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (rc == NGX_DONE) { | 
|  |  | 
|  | /* a whole response has been parsed successfully */ | 
|  |  | 
|  | r->headers_in.content_length_n = 0; | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (rc == NGX_AGAIN) { | 
|  |  | 
|  | /* set amount of data we want to see next time */ | 
|  |  | 
|  | r->headers_in.content_length_n = rb->chunked->length; | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* invalid */ | 
|  |  | 
|  | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, | 
|  | "client sent invalid chunked body"); | 
|  |  | 
|  | return NGX_HTTP_BAD_REQUEST; | 
|  | } | 
|  |  | 
|  | } else { | 
|  | size = b->last - b->pos; | 
|  |  | 
|  | if ((off_t) size > r->headers_in.content_length_n) { | 
|  | b->pos += r->headers_in.content_length_n; | 
|  | r->headers_in.content_length_n = 0; | 
|  |  | 
|  | } else { | 
|  | b->pos = b->last; | 
|  | r->headers_in.content_length_n -= size; | 
|  | } | 
|  | } | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_test_expect(ngx_http_request_t *r) | 
|  | { | 
|  | ngx_int_t   n; | 
|  | ngx_str_t  *expect; | 
|  |  | 
|  | if (r->expect_tested | 
|  | || r->headers_in.expect == NULL | 
|  | || r->http_version < NGX_HTTP_VERSION_11) | 
|  | { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | r->expect_tested = 1; | 
|  |  | 
|  | expect = &r->headers_in.expect->value; | 
|  |  | 
|  | if (expect->len != sizeof("100-continue") - 1 | 
|  | || ngx_strncasecmp(expect->data, (u_char *) "100-continue", | 
|  | sizeof("100-continue") - 1) | 
|  | != 0) | 
|  | { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "send 100 Continue"); | 
|  |  | 
|  | n = r->connection->send(r->connection, | 
|  | (u_char *) "HTTP/1.1 100 Continue" CRLF CRLF, | 
|  | sizeof("HTTP/1.1 100 Continue" CRLF CRLF) - 1); | 
|  |  | 
|  | if (n == sizeof("HTTP/1.1 100 Continue" CRLF CRLF) - 1) { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | /* we assume that such small packet should be send successfully */ | 
|  |  | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) | 
|  | { | 
|  | if (r->headers_in.chunked) { | 
|  | return ngx_http_request_body_chunked_filter(r, in); | 
|  |  | 
|  | } else { | 
|  | return ngx_http_request_body_length_filter(r, in); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in) | 
|  | { | 
|  | size_t                     size; | 
|  | ngx_int_t                  rc; | 
|  | ngx_buf_t                 *b; | 
|  | ngx_chain_t               *cl, *tl, *out, **ll; | 
|  | ngx_http_request_body_t   *rb; | 
|  |  | 
|  | rb = r->request_body; | 
|  |  | 
|  | if (rb->rest == -1) { | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http request body content length filter"); | 
|  |  | 
|  | rb->rest = r->headers_in.content_length_n; | 
|  | } | 
|  |  | 
|  | out = NULL; | 
|  | ll = &out; | 
|  |  | 
|  | for (cl = in; cl; cl = cl->next) { | 
|  |  | 
|  | tl = ngx_chain_get_free_buf(r->pool, &rb->free); | 
|  | if (tl == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | b = tl->buf; | 
|  |  | 
|  | ngx_memzero(b, sizeof(ngx_buf_t)); | 
|  |  | 
|  | b->temporary = 1; | 
|  | b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; | 
|  | b->start = cl->buf->pos; | 
|  | b->pos = cl->buf->pos; | 
|  | b->last = cl->buf->last; | 
|  | b->end = cl->buf->end; | 
|  |  | 
|  | size = cl->buf->last - cl->buf->pos; | 
|  |  | 
|  | if ((off_t) size < rb->rest) { | 
|  | cl->buf->pos = cl->buf->last; | 
|  | rb->rest -= size; | 
|  |  | 
|  | } else { | 
|  | cl->buf->pos += rb->rest; | 
|  | rb->rest = 0; | 
|  | b->last = cl->buf->pos; | 
|  | b->last_buf = 1; | 
|  | } | 
|  |  | 
|  | *ll = tl; | 
|  | ll = &tl->next; | 
|  | } | 
|  |  | 
|  | rc = ngx_http_request_body_save_filter(r, out); | 
|  |  | 
|  | ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, | 
|  | (ngx_buf_tag_t) &ngx_http_read_client_request_body); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_request_body_chunked_filter(ngx_http_request_t *r, ngx_chain_t *in) | 
|  | { | 
|  | size_t                     size; | 
|  | ngx_int_t                  rc; | 
|  | ngx_buf_t                 *b; | 
|  | ngx_chain_t               *cl, *out, *tl, **ll; | 
|  | ngx_http_request_body_t   *rb; | 
|  | ngx_http_core_loc_conf_t  *clcf; | 
|  |  | 
|  | rb = r->request_body; | 
|  |  | 
|  | if (rb->rest == -1) { | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http request body chunked filter"); | 
|  |  | 
|  | rb->chunked = ngx_pcalloc(r->pool, sizeof(ngx_http_chunked_t)); | 
|  | if (rb->chunked == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | r->headers_in.content_length_n = 0; | 
|  | rb->rest = 3; | 
|  | } | 
|  |  | 
|  | out = NULL; | 
|  | ll = &out; | 
|  |  | 
|  | for (cl = in; cl; cl = cl->next) { | 
|  |  | 
|  | for ( ;; ) { | 
|  |  | 
|  | ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, | 
|  | "http body chunked buf " | 
|  | "t:%d f:%d %p, pos %p, size: %z file: %O, size: %z", | 
|  | cl->buf->temporary, cl->buf->in_file, | 
|  | cl->buf->start, cl->buf->pos, | 
|  | cl->buf->last - cl->buf->pos, | 
|  | cl->buf->file_pos, | 
|  | cl->buf->file_last - cl->buf->file_pos); | 
|  |  | 
|  | rc = ngx_http_parse_chunked(r, cl->buf, rb->chunked); | 
|  |  | 
|  | if (rc == NGX_OK) { | 
|  |  | 
|  | /* a chunk has been parsed successfully */ | 
|  |  | 
|  | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); | 
|  |  | 
|  | if (clcf->client_max_body_size | 
|  | && clcf->client_max_body_size | 
|  | < r->headers_in.content_length_n + rb->chunked->size) | 
|  | { | 
|  | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, | 
|  | "client intended to send too large chunked " | 
|  | "body: %O bytes", | 
|  | r->headers_in.content_length_n | 
|  | + rb->chunked->size); | 
|  |  | 
|  | r->lingering_close = 1; | 
|  |  | 
|  | return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; | 
|  | } | 
|  |  | 
|  | tl = ngx_chain_get_free_buf(r->pool, &rb->free); | 
|  | if (tl == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | b = tl->buf; | 
|  |  | 
|  | ngx_memzero(b, sizeof(ngx_buf_t)); | 
|  |  | 
|  | b->temporary = 1; | 
|  | b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; | 
|  | b->start = cl->buf->pos; | 
|  | b->pos = cl->buf->pos; | 
|  | b->last = cl->buf->last; | 
|  | b->end = cl->buf->end; | 
|  |  | 
|  | *ll = tl; | 
|  | ll = &tl->next; | 
|  |  | 
|  | size = cl->buf->last - cl->buf->pos; | 
|  |  | 
|  | if ((off_t) size > rb->chunked->size) { | 
|  | cl->buf->pos += rb->chunked->size; | 
|  | r->headers_in.content_length_n += rb->chunked->size; | 
|  | rb->chunked->size = 0; | 
|  |  | 
|  | } else { | 
|  | rb->chunked->size -= size; | 
|  | r->headers_in.content_length_n += size; | 
|  | cl->buf->pos = cl->buf->last; | 
|  | } | 
|  |  | 
|  | b->last = cl->buf->pos; | 
|  |  | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (rc == NGX_DONE) { | 
|  |  | 
|  | /* a whole response has been parsed successfully */ | 
|  |  | 
|  | rb->rest = 0; | 
|  |  | 
|  | tl = ngx_chain_get_free_buf(r->pool, &rb->free); | 
|  | if (tl == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | b = tl->buf; | 
|  |  | 
|  | ngx_memzero(b, sizeof(ngx_buf_t)); | 
|  |  | 
|  | b->last_buf = 1; | 
|  |  | 
|  | *ll = tl; | 
|  | ll = &tl->next; | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (rc == NGX_AGAIN) { | 
|  |  | 
|  | /* set rb->rest, amount of data we want to see next time */ | 
|  |  | 
|  | rb->rest = rb->chunked->length; | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* invalid */ | 
|  |  | 
|  | ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, | 
|  | "client sent invalid chunked body"); | 
|  |  | 
|  | return NGX_HTTP_BAD_REQUEST; | 
|  | } | 
|  | } | 
|  |  | 
|  | rc = ngx_http_request_body_save_filter(r, out); | 
|  |  | 
|  | ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, | 
|  | (ngx_buf_tag_t) &ngx_http_read_client_request_body); | 
|  |  | 
|  | return rc; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in) | 
|  | { | 
|  | #if (NGX_DEBUG) | 
|  | ngx_chain_t               *cl; | 
|  | #endif | 
|  | ngx_http_request_body_t   *rb; | 
|  |  | 
|  | rb = r->request_body; | 
|  |  | 
|  | #if (NGX_DEBUG) | 
|  |  | 
|  | for (cl = rb->bufs; cl; cl = cl->next) { | 
|  | ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, | 
|  | "http body old buf t:%d f:%d %p, pos %p, size: %z " | 
|  | "file: %O, size: %z", | 
|  | cl->buf->temporary, cl->buf->in_file, | 
|  | cl->buf->start, cl->buf->pos, | 
|  | cl->buf->last - cl->buf->pos, | 
|  | cl->buf->file_pos, | 
|  | cl->buf->file_last - cl->buf->file_pos); | 
|  | } | 
|  |  | 
|  | for (cl = in; cl; cl = cl->next) { | 
|  | ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, | 
|  | "http body new buf t:%d f:%d %p, pos %p, size: %z " | 
|  | "file: %O, size: %z", | 
|  | cl->buf->temporary, cl->buf->in_file, | 
|  | cl->buf->start, cl->buf->pos, | 
|  | cl->buf->last - cl->buf->pos, | 
|  | cl->buf->file_pos, | 
|  | cl->buf->file_last - cl->buf->file_pos); | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | /* TODO: coalesce neighbouring buffers */ | 
|  |  | 
|  | if (ngx_chain_add_copy(r->pool, &rb->bufs, in) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | return NGX_OK; | 
|  | } |