|  |  | 
|  | /* | 
|  | * Copyright (C) Igor Sysoev | 
|  | */ | 
|  |  | 
|  |  | 
|  | #include <ngx_config.h> | 
|  | #include <ngx_core.h> | 
|  | #include <ngx_event.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, | 
|  | ngx_chain_t *body); | 
|  | static void ngx_http_read_discarded_request_body_handler(ngx_http_request_t *r); | 
|  | static ngx_int_t ngx_http_read_discarded_request_body(ngx_http_request_t *r); | 
|  |  | 
|  |  | 
|  | /* | 
|  | * on completion ngx_http_read_client_request_body() adds to | 
|  | * r->request_body->bufs one or two bufs: | 
|  | *    *) one memory buf that was preread in r->header_in; | 
|  | *    *) one memory or file buf that contains the rest of the body | 
|  | */ | 
|  |  | 
|  | 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_buf_t                 *b; | 
|  | ngx_chain_t               *cl, **next; | 
|  | ngx_temp_file_t           *tf; | 
|  | ngx_http_request_body_t   *rb; | 
|  | ngx_http_core_loc_conf_t  *clcf; | 
|  |  | 
|  | if (r->request_body || r->discard_body) { | 
|  | post_handler(r); | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); | 
|  | if (rb == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | r->request_body = rb; | 
|  |  | 
|  | if (r->headers_in.content_length_n < 0) { | 
|  | post_handler(r); | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); | 
|  |  | 
|  | if (r->headers_in.content_length_n == 0) { | 
|  |  | 
|  | if (r->request_body_in_file_only) { | 
|  | tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); | 
|  | if (tf == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | 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 (ngx_create_temp_file(&tf->file, tf->path, tf->pool, | 
|  | tf->persistent, tf->clean, tf->access) | 
|  | != NGX_OK) | 
|  | { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | post_handler(r); | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | rb->post_handler = post_handler; | 
|  |  | 
|  | /* | 
|  | * set by ngx_pcalloc(): | 
|  | * | 
|  | *     rb->bufs = NULL; | 
|  | *     rb->buf = NULL; | 
|  | *     rb->rest = 0; | 
|  | */ | 
|  |  | 
|  | 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); | 
|  |  | 
|  | b = ngx_calloc_buf(r->pool); | 
|  | if (b == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | 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->bufs = ngx_alloc_chain_link(r->pool); | 
|  | if (rb->bufs == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | rb->bufs->buf = b; | 
|  | rb->bufs->next = NULL; | 
|  |  | 
|  | rb->buf = b; | 
|  |  | 
|  | if ((off_t) preread >= r->headers_in.content_length_n) { | 
|  |  | 
|  | /* the whole request body was pre-read */ | 
|  |  | 
|  | r->header_in->pos += (size_t) r->headers_in.content_length_n; | 
|  | r->request_length += r->headers_in.content_length_n; | 
|  |  | 
|  | if (r->request_body_in_file_only) { | 
|  | if (ngx_http_write_request_body(r, rb->bufs) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | post_handler(r); | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * to not consider the body as pipelined request in | 
|  | * ngx_http_set_keepalive() | 
|  | */ | 
|  | r->header_in->pos = r->header_in->last; | 
|  |  | 
|  | r->request_length += preread; | 
|  |  | 
|  | rb->rest = r->headers_in.content_length_n - preread; | 
|  |  | 
|  | if (rb->rest <= (off_t) (b->end - b->last)) { | 
|  |  | 
|  | /* the whole request body may be placed in r->header_in */ | 
|  |  | 
|  | rb->to_write = rb->bufs; | 
|  |  | 
|  | r->read_event_handler = ngx_http_read_client_request_body_handler; | 
|  |  | 
|  | return ngx_http_do_read_client_request_body(r); | 
|  | } | 
|  |  | 
|  | next = &rb->bufs->next; | 
|  |  | 
|  | } else { | 
|  | b = NULL; | 
|  | rb->rest = r->headers_in.content_length_n; | 
|  | next = &rb->bufs; | 
|  | } | 
|  |  | 
|  | size = clcf->client_body_buffer_size; | 
|  | size += size >> 2; | 
|  |  | 
|  | if (rb->rest < size) { | 
|  | size = (ssize_t) rb->rest; | 
|  |  | 
|  | if (r->request_body_in_single_buf) { | 
|  | size += preread; | 
|  | } | 
|  |  | 
|  | } else { | 
|  | size = clcf->client_body_buffer_size; | 
|  |  | 
|  | /* disable copying buffer for r->request_body_in_single_buf */ | 
|  | b = NULL; | 
|  | } | 
|  |  | 
|  | rb->buf = ngx_create_temp_buf(r->pool, size); | 
|  | if (rb->buf == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | cl = ngx_alloc_chain_link(r->pool); | 
|  | if (cl == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | cl->buf = rb->buf; | 
|  | cl->next = NULL; | 
|  |  | 
|  | if (b && r->request_body_in_single_buf) { | 
|  | size = b->last - b->pos; | 
|  | ngx_memcpy(rb->buf->pos, b->pos, size); | 
|  | rb->buf->last += size; | 
|  |  | 
|  | next = &rb->bufs; | 
|  | } | 
|  |  | 
|  | *next = cl; | 
|  |  | 
|  | if (r->request_body_in_file_only || r->request_body_in_single_buf) { | 
|  | rb->to_write = rb->bufs; | 
|  |  | 
|  | } else { | 
|  | rb->to_write = rb->bufs->next ? rb->bufs->next : rb->bufs; | 
|  | } | 
|  |  | 
|  | r->read_event_handler = ngx_http_read_client_request_body_handler; | 
|  |  | 
|  | return ngx_http_do_read_client_request_body(r); | 
|  | } | 
|  |  | 
|  |  | 
|  | 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) | 
|  | { | 
|  | size_t                     size; | 
|  | ssize_t                    n; | 
|  | ngx_buf_t                 *b; | 
|  | 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) { | 
|  |  | 
|  | if (ngx_http_write_request_body(r, rb->to_write) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | rb->to_write = rb->bufs->next ? rb->bufs->next : rb->bufs; | 
|  | rb->buf->last = rb->buf->start; | 
|  | } | 
|  |  | 
|  | size = rb->buf->end - rb->buf->last; | 
|  |  | 
|  | if ((off_t) size > rb->rest) { | 
|  | size = (size_t) rb->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 closed prematurely connection"); | 
|  | } | 
|  |  | 
|  | if (n == 0 || n == NGX_ERROR) { | 
|  | c->error = 1; | 
|  | return NGX_HTTP_BAD_REQUEST; | 
|  | } | 
|  |  | 
|  | rb->buf->last += n; | 
|  | rb->rest -= n; | 
|  | r->request_length += n; | 
|  |  | 
|  | 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_ERROR) { | 
|  | 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, rb->to_write) != NGX_OK) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | b = ngx_calloc_buf(r->pool); | 
|  | if (b == NULL) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | b->in_file = 1; | 
|  | b->file_pos = 0; | 
|  | b->file_last = rb->temp_file->file.offset; | 
|  | b->file = &rb->temp_file->file; | 
|  |  | 
|  | if (rb->bufs->next) { | 
|  | rb->bufs->next->buf = b; | 
|  |  | 
|  | } else { | 
|  | rb->bufs->buf = b; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (r->request_body_in_file_only && rb->bufs->next) { | 
|  | rb->bufs = rb->bufs->next; | 
|  | } | 
|  |  | 
|  | rb->post_handler(r); | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static ngx_int_t | 
|  | ngx_http_write_request_body(ngx_http_request_t *r, ngx_chain_t *body) | 
|  | { | 
|  | ssize_t                    n; | 
|  | ngx_temp_file_t           *tf; | 
|  | ngx_http_request_body_t   *rb; | 
|  | ngx_http_core_loc_conf_t  *clcf; | 
|  |  | 
|  | rb = r->request_body; | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | n = ngx_write_chain_to_temp_file(rb->temp_file, body); | 
|  |  | 
|  | /* TODO: n == 0 or not complete and level event */ | 
|  |  | 
|  | if (n == NGX_ERROR) { | 
|  | return NGX_ERROR; | 
|  | } | 
|  |  | 
|  | rb->temp_file->offset += n; | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | ngx_int_t | 
|  | ngx_http_discard_request_body(ngx_http_request_t *r) | 
|  | { | 
|  | ssize_t       size; | 
|  | ngx_event_t  *rev; | 
|  |  | 
|  | if (r != r->main || r->discard_body) { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | 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->request_body) { | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | size = r->header_in->last - r->header_in->pos; | 
|  |  | 
|  | if (size) { | 
|  | if (r->headers_in.content_length_n > size) { | 
|  | r->headers_in.content_length_n -= size; | 
|  |  | 
|  | } else { | 
|  | r->header_in->pos += (size_t) r->headers_in.content_length_n; | 
|  | r->headers_in.content_length_n = 0; | 
|  | return NGX_OK; | 
|  | } | 
|  | } | 
|  |  | 
|  | r->discard_body = 1; | 
|  |  | 
|  | r->read_event_handler = ngx_http_read_discarded_request_body_handler; | 
|  |  | 
|  | if (ngx_handle_read_event(rev, 0) == NGX_ERROR) { | 
|  | return NGX_HTTP_INTERNAL_SERVER_ERROR; | 
|  | } | 
|  |  | 
|  | (void) ngx_http_read_discarded_request_body(r); | 
|  |  | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  |  | 
|  | static void | 
|  | ngx_http_read_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, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (r->lingering_time) { | 
|  | timer = (ngx_msec_t) (r->lingering_time - ngx_time()); | 
|  |  | 
|  | if (timer <= 0) { | 
|  | r->discard_body = 0; | 
|  | ngx_http_finalize_request(r, 0); | 
|  | return; | 
|  | } | 
|  |  | 
|  | } else { | 
|  | timer = 0; | 
|  | } | 
|  |  | 
|  | rc = ngx_http_read_discarded_request_body(r); | 
|  |  | 
|  | if (rc == NGX_OK) { | 
|  |  | 
|  | r->discard_body = 0; | 
|  |  | 
|  | if (r->done) { | 
|  | ngx_http_finalize_request(r, 0); | 
|  | } | 
|  |  | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* rc == NGX_AGAIN */ | 
|  |  | 
|  | if (ngx_handle_read_event(rev, 0) == NGX_ERROR) { | 
|  | c->error = 1; | 
|  | ngx_http_finalize_request(r, rc); | 
|  | 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; | 
|  | u_char   buffer[NGX_HTTP_DISCARD_BUFFER_SIZE]; | 
|  |  | 
|  | ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, | 
|  | "http read discarded body"); | 
|  |  | 
|  | do { | 
|  | if (r->headers_in.content_length_n == 0) { | 
|  | r->read_event_handler = ngx_http_block_reading; | 
|  | return NGX_OK; | 
|  | } | 
|  |  | 
|  | size = (r->headers_in.content_length_n > NGX_HTTP_DISCARD_BUFFER_SIZE) ? | 
|  | NGX_HTTP_DISCARD_BUFFER_SIZE: | 
|  | (size_t) r->headers_in.content_length_n; | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | r->headers_in.content_length_n -= n; | 
|  |  | 
|  | } while (r->connection->read->ready); | 
|  |  | 
|  | return NGX_AGAIN; | 
|  | } |