From 757cbc4b9ba97c8485cbf3a5d29d4cd94fd241a7 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 15:16:07 +0000 Subject: [PATCH 1/4] commit patch 19665754 --- src/http/v2/ngx_http_v2.c | 12 +- src/http/v2/ngx_http_v2.c.orig | 4055 ++++++++++++++++++++++++++++++++ 2 files changed, 4063 insertions(+), 4 deletions(-) create mode 100644 src/http/v2/ngx_http_v2.c.orig diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 869ce08943..8619729e3c 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -1554,6 +1554,14 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, header->name.len = h2c->state.field_end - h2c->state.field_start; header->name.data = h2c->state.field_start; + if (header->name.len == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent zero header name length"); + + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_PROTOCOL_ERROR); + } + return ngx_http_v2_state_field_len(h2c, pos, end); } @@ -2945,10 +2953,6 @@ ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) ngx_uint_t i; ngx_http_core_srv_conf_t *cscf; - if (header->name.len == 0) { - return NGX_ERROR; - } - r->invalid_header = 0; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); diff --git a/src/http/v2/ngx_http_v2.c.orig b/src/http/v2/ngx_http_v2.c.orig new file mode 100644 index 0000000000..869ce08943 --- /dev/null +++ b/src/http/v2/ngx_http_v2.c.orig @@ -0,0 +1,4055 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Valentin V. Bartenev + */ + + +#include +#include +#include +#include + + +/* errors */ +#define NGX_HTTP_V2_NO_ERROR 0x0 +#define NGX_HTTP_V2_PROTOCOL_ERROR 0x1 +#define NGX_HTTP_V2_INTERNAL_ERROR 0x2 +#define NGX_HTTP_V2_FLOW_CTRL_ERROR 0x3 +#define NGX_HTTP_V2_SETTINGS_TIMEOUT 0x4 +#define NGX_HTTP_V2_STREAM_CLOSED 0x5 +#define NGX_HTTP_V2_SIZE_ERROR 0x6 +#define NGX_HTTP_V2_REFUSED_STREAM 0x7 +#define NGX_HTTP_V2_CANCEL 0x8 +#define NGX_HTTP_V2_COMP_ERROR 0x9 +#define NGX_HTTP_V2_CONNECT_ERROR 0xa +#define NGX_HTTP_V2_ENHANCE_YOUR_CALM 0xb +#define NGX_HTTP_V2_INADEQUATE_SECURITY 0xc +#define NGX_HTTP_V2_HTTP_1_1_REQUIRED 0xd + +/* frame sizes */ +#define NGX_HTTP_V2_RST_STREAM_SIZE 4 +#define NGX_HTTP_V2_PRIORITY_SIZE 5 +#define NGX_HTTP_V2_PING_SIZE 8 +#define NGX_HTTP_V2_GOAWAY_SIZE 8 +#define NGX_HTTP_V2_WINDOW_UPDATE_SIZE 4 + +#define NGX_HTTP_V2_STREAM_ID_SIZE 4 + +#define NGX_HTTP_V2_SETTINGS_PARAM_SIZE 6 + +/* settings fields */ +#define NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING 0x1 +#define NGX_HTTP_V2_MAX_STREAMS_SETTING 0x3 +#define NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING 0x4 +#define NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING 0x5 + +#define NGX_HTTP_V2_FRAME_BUFFER_SIZE 24 + +#define NGX_HTTP_V2_DEFAULT_FRAME_SIZE (1 << 14) + +#define NGX_HTTP_V2_MAX_WINDOW ((1U << 31) - 1) +#define NGX_HTTP_V2_DEFAULT_WINDOW 65535 + +#define NGX_HTTP_V2_ROOT (void *) -1 + + +static void ngx_http_v2_read_handler(ngx_event_t *rev); +static void ngx_http_v2_write_handler(ngx_event_t *wev); +static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c); + +static u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_header_block(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_field_huff(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_field_raw(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_field_skip(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_header_complete(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end, ngx_http_v2_handler_pt handler); +static u_char *ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_push_promise(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_goaway(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_window_update(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_continuation(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_complete(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_skip_padded(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_skip(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_skip_headers(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +static u_char *ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c, + u_char *pos, u_char *end, ngx_http_v2_handler_pt handler); +static u_char *ngx_http_v2_connection_error(ngx_http_v2_connection_t *h2c, + ngx_uint_t err); + +static ngx_int_t ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, + u_char **pos, u_char *end, ngx_uint_t prefix); + +static ngx_http_v2_stream_t *ngx_http_v2_create_stream( + ngx_http_v2_connection_t *h2c); +static ngx_http_v2_node_t *ngx_http_v2_get_node_by_id( + ngx_http_v2_connection_t *h2c, ngx_uint_t sid, ngx_uint_t alloc); +static ngx_http_v2_node_t *ngx_http_v2_get_closed_node( + ngx_http_v2_connection_t *h2c); +#define ngx_http_v2_index_size(h2scf) (h2scf->streams_index_mask + 1) +#define ngx_http_v2_index(h2scf, sid) ((sid >> 1) & h2scf->streams_index_mask) + +static ngx_int_t ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c, + ngx_uint_t ack); +static ngx_int_t ngx_http_v2_settings_frame_handler( + ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); +static ngx_int_t ngx_http_v2_send_window_update(ngx_http_v2_connection_t *h2c, + ngx_uint_t sid, size_t window); +static ngx_int_t ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c, + ngx_uint_t sid, ngx_uint_t status); + +static ngx_http_v2_out_frame_t *ngx_http_v2_get_frame( + ngx_http_v2_connection_t *h2c, size_t length, ngx_uint_t type, + u_char flags, ngx_uint_t sid); +static ngx_int_t ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame); + +static ngx_int_t ngx_http_v2_validate_header(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_pseudo_header(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_path(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_method(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r, + ngx_http_v2_header_t *header); +static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r); +static void ngx_http_v2_run_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_v2_init_request_body(ngx_http_request_t *r); + +static ngx_int_t ngx_http_v2_terminate_stream(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream, ngx_uint_t status); +static void ngx_http_v2_close_stream_handler(ngx_event_t *ev); +static void ngx_http_v2_handle_connection_handler(ngx_event_t *rev); +static void ngx_http_v2_idle_handler(ngx_event_t *rev); +static void ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, + ngx_uint_t status); + +static ngx_int_t ngx_http_v2_adjust_windows(ngx_http_v2_connection_t *h2c, + ssize_t delta); +static void ngx_http_v2_set_dependency(ngx_http_v2_connection_t *h2c, + ngx_http_v2_node_t *node, ngx_uint_t depend, ngx_uint_t exclusive); +static void ngx_http_v2_node_children_update(ngx_http_v2_node_t *node); + +static void ngx_http_v2_pool_cleanup(void *data); + + +static ngx_http_v2_handler_pt ngx_http_v2_frame_states[] = { + ngx_http_v2_state_data, + ngx_http_v2_state_headers, + ngx_http_v2_state_priority, + ngx_http_v2_state_rst_stream, + ngx_http_v2_state_settings, + ngx_http_v2_state_push_promise, + ngx_http_v2_state_ping, + ngx_http_v2_state_goaway, + ngx_http_v2_state_window_update, + ngx_http_v2_state_continuation +}; + +#define NGX_HTTP_V2_FRAME_STATES \ + (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt)) + + +void +ngx_http_v2_init(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_v2_main_conf_t *h2mcf; + ngx_http_v2_connection_t *h2c; + + c = rev->data; + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "init http2 connection"); + + c->log->action = "processing HTTP/2 connection"; + + h2mcf = ngx_http_get_module_main_conf(hc->conf_ctx, ngx_http_v2_module); + + if (h2mcf->recv_buffer == NULL) { + h2mcf->recv_buffer = ngx_palloc(ngx_cycle->pool, + h2mcf->recv_buffer_size); + if (h2mcf->recv_buffer == NULL) { + ngx_http_close_connection(c); + return; + } + } + + h2c = ngx_pcalloc(c->pool, sizeof(ngx_http_v2_connection_t)); + if (h2c == NULL) { + ngx_http_close_connection(c); + return; + } + + h2c->connection = c; + h2c->http_connection = hc; + + h2c->send_window = NGX_HTTP_V2_DEFAULT_WINDOW; + h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + h2c->init_window = NGX_HTTP_V2_DEFAULT_WINDOW; + + h2c->frame_size = NGX_HTTP_V2_DEFAULT_FRAME_SIZE; + + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); + if (h2c->pool == NULL) { + ngx_http_close_connection(c); + return; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_pool_cleanup_file_t)); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v2_pool_cleanup; + cln->data = h2c; + + h2c->streams_index = ngx_pcalloc(c->pool, ngx_http_v2_index_size(h2scf) + * sizeof(ngx_http_v2_node_t *)); + if (h2c->streams_index == NULL) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_v2_send_settings(h2c, 0) == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW + - NGX_HTTP_V2_DEFAULT_WINDOW) + == NGX_ERROR) + { + ngx_http_close_connection(c); + return; + } + + h2c->state.handler = hc->proxy_protocol ? ngx_http_v2_state_proxy_protocol + : ngx_http_v2_state_preface; + + ngx_queue_init(&h2c->waiting); + ngx_queue_init(&h2c->posted); + ngx_queue_init(&h2c->dependencies); + ngx_queue_init(&h2c->closed); + + c->data = h2c; + + rev->handler = ngx_http_v2_read_handler; + c->write->handler = ngx_http_v2_write_handler; + + ngx_http_v2_read_handler(rev); +} + + +static void +ngx_http_v2_read_handler(ngx_event_t *rev) +{ + u_char *p, *end; + size_t available; + ssize_t n; + ngx_connection_t *c; + ngx_http_v2_main_conf_t *h2mcf; + ngx_http_v2_connection_t *h2c; + + c = rev->data; + h2c = c->data; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 read handler"); + + h2c->blocked = 1; + + h2mcf = ngx_http_get_module_main_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + available = h2mcf->recv_buffer_size - 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE; + + do { + p = h2mcf->recv_buffer; + + ngx_memcpy(p, h2c->state.buffer, NGX_HTTP_V2_STATE_BUFFER_SIZE); + end = p + h2c->state.buffer_used; + + n = c->recv(c, end, available); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0 && (h2c->state.incomplete || h2c->processing)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + c->error = 1; + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + end += n; + + h2c->state.buffer_used = 0; + h2c->state.incomplete = 0; + + do { + p = h2c->state.handler(h2c, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + } while (rev->ready); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + return; + } + + if (h2c->last_out && ngx_http_v2_send_output_queue(h2c) == NGX_ERROR) { + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + h2c->blocked = 0; + + if (h2c->processing) { + if (rev->timer_set) { + ngx_del_timer(rev); + } + + return; + } + + ngx_http_v2_handle_connection(h2c); +} + + +static void +ngx_http_v2_write_handler(ngx_event_t *wev) +{ + ngx_int_t rc; + ngx_queue_t *q; + ngx_connection_t *c; + ngx_http_v2_stream_t *stream; + ngx_http_v2_connection_t *h2c; + + c = wev->data; + h2c = c->data; + + if (wev->timedout) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http2 write event timed out"); + c->error = 1; + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 write handler"); + + h2c->blocked = 1; + + rc = ngx_http_v2_send_output_queue(h2c); + + if (rc == NGX_ERROR) { + ngx_http_v2_finalize_connection(h2c, 0); + return; + } + + while (!ngx_queue_empty(&h2c->posted)) { + q = ngx_queue_head(&h2c->posted); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue); + + stream->handled = 0; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "run http2 stream %ui", stream->node->id); + + wev = stream->request->connection->write; + wev->handler(wev); + } + + h2c->blocked = 0; + + if (rc == NGX_AGAIN) { + return; + } + + ngx_http_v2_handle_connection(h2c); +} + + +ngx_int_t +ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c) +{ + int tcp_nodelay; + ngx_chain_t *cl; + ngx_event_t *wev; + ngx_connection_t *c; + ngx_http_v2_out_frame_t *out, *frame, *fn; + ngx_http_core_loc_conf_t *clcf; + + c = h2c->connection; + + if (c->error) { + return NGX_ERROR; + } + + wev = c->write; + + if (!wev->ready) { + return NGX_OK; + } + + cl = NULL; + out = NULL; + + for (frame = h2c->last_out; frame; frame = fn) { + frame->last->next = cl; + cl = frame->first; + + fn = frame->next; + frame->next = out; + out = frame; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http2 frame out: %p sid:%ui bl:%d len:%uz", + out, out->stream ? out->stream->node->id : 0, + out->blocked, out->length); + } + + cl = c->send_chain(c, cl, 0); + + if (cl == NGX_CHAIN_ERROR) { + goto error; + } + + clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx, + ngx_http_core_module); + + if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) { + goto error; + } + + if (c->tcp_nopush == NGX_TCP_NOPUSH_SET) { + if (ngx_tcp_push(c->fd) == -1) { + ngx_connection_error(c, ngx_socket_errno, ngx_tcp_push_n " failed"); + goto error; + } + + c->tcp_nopush = NGX_TCP_NOPUSH_UNSET; + tcp_nodelay = ngx_tcp_nodelay_and_tcp_nopush ? 1 : 0; + + } else { + tcp_nodelay = 1; + } + + if (tcp_nodelay + && clcf->tcp_nodelay + && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay"); + + if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, + (const void *) &tcp_nodelay, sizeof(int)) + == -1) + { +#if (NGX_SOLARIS) + /* Solaris returns EINVAL if a socket has been shut down */ + c->log_error = NGX_ERROR_IGNORE_EINVAL; +#endif + + ngx_connection_error(c, ngx_socket_errno, + "setsockopt(TCP_NODELAY) failed"); + + c->log_error = NGX_ERROR_INFO; + goto error; + } + + c->tcp_nodelay = NGX_TCP_NODELAY_SET; + } + + if (cl) { + ngx_add_timer(wev, clcf->send_timeout); + + } else { + if (wev->timer_set) { + ngx_del_timer(wev); + } + } + + for ( /* void */ ; out; out = fn) { + fn = out->next; + + if (out->handler(h2c, out) != NGX_OK) { + out->blocked = 1; + break; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http2 frame sent: %p sid:%ui bl:%d len:%uz", + out, out->stream ? out->stream->node->id : 0, + out->blocked, out->length); + } + + frame = NULL; + + for ( /* void */ ; out; out = fn) { + fn = out->next; + out->next = frame; + frame = out; + } + + h2c->last_out = frame; + + return NGX_OK; + +error: + + c->error = 1; + + if (!h2c->blocked) { + ngx_post_event(wev, &ngx_posted_events); + } + + return NGX_ERROR; +} + + +static void +ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) +{ + ngx_connection_t *c; + ngx_http_v2_srv_conf_t *h2scf; + + if (h2c->last_out || h2c->processing) { + return; + } + + c = h2c->connection; + + if (c->error) { + ngx_http_close_connection(c); + return; + } + + if (c->buffered) { + return; + } + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + if (h2c->state.incomplete) { + ngx_add_timer(c->read, h2scf->recv_timeout); + return; + } + + if (ngx_terminate || ngx_exiting) { + ngx_http_close_connection(c); + return; + } + + ngx_destroy_pool(h2c->pool); + + h2c->pool = NULL; + h2c->free_frames = NULL; + h2c->free_fake_connections = NULL; + +#if (NGX_HTTP_SSL) + if (c->ssl) { + ngx_ssl_free_buffer(c); + } +#endif + + c->destroyed = 1; + c->idle = 1; + ngx_reusable_connection(c, 1); + + c->write->handler = ngx_http_empty_handler; + c->read->handler = ngx_http_v2_idle_handler; + + if (c->write->timer_set) { + ngx_del_timer(c->write); + } + + ngx_add_timer(c->read, h2scf->idle_timeout); +} + + +static u_char * +ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_log_t *log; + + log = h2c->connection->log; + log->action = "reading PROXY protocol"; + + pos = ngx_proxy_protocol_read(h2c->connection, pos, end); + + log->action = "processing HTTP/2 connection"; + + if (pos == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + return ngx_http_v2_state_preface(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + static const u_char preface[] = "PRI * HTTP/2.0\r\n"; + + if ((size_t) (end - pos) < sizeof(preface) - 1) { + return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_preface); + } + + if (ngx_memcmp(pos, preface, sizeof(preface) - 1) != 0) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "invalid http2 connection preface \"%*s\"", + sizeof(preface) - 1, pos); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + return ngx_http_v2_state_preface_end(h2c, pos + sizeof(preface) - 1, end); +} + + +static u_char * +ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + static const u_char preface[] = "\r\nSM\r\n\r\n"; + + if ((size_t) (end - pos) < sizeof(preface) - 1) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_preface_end); + } + + if (ngx_memcmp(pos, preface, sizeof(preface) - 1) != 0) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "invalid http2 connection preface \"%*s\"", + sizeof(preface) - 1, pos); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 preface verified"); + + return ngx_http_v2_state_head(h2c, pos + sizeof(preface) - 1, end); +} + + +static u_char * +ngx_http_v2_state_head(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) +{ + uint32_t head; + ngx_uint_t type; + + if (end - pos < NGX_HTTP_V2_FRAME_HEADER_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_head); + } + + head = ngx_http_v2_parse_uint32(pos); + + h2c->state.length = ngx_http_v2_parse_length(head); + h2c->state.flags = pos[4]; + + h2c->state.sid = ngx_http_v2_parse_sid(&pos[5]); + + pos += NGX_HTTP_V2_FRAME_HEADER_SIZE; + + type = ngx_http_v2_parse_type(head); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "process http2 frame type:%ui f:%Xd l:%uz sid:%ui", + type, h2c->state.flags, h2c->state.length, h2c->state.sid); + + if (type >= NGX_HTTP_V2_FRAME_STATES) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame with unknown type %ui", type); + return ngx_http_v2_state_skip(h2c, pos, end); + } + + return ngx_http_v2_frame_states[type](h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_data(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) +{ + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + + if (h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG) { + + if (h2c->state.length == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent padded DATA frame " + "with incorrect length: %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos == 0) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_data); + } + + h2c->state.padding = *pos++; + h2c->state.length--; + + if (h2c->state.padding > h2c->state.length) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent padded DATA frame " + "with incorrect length: %uz, padding: %uz", + h2c->state.length, h2c->state.padding); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + h2c->state.length -= h2c->state.padding; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 DATA frame"); + + if (h2c->state.length > h2c->recv_window) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client violated connection flow control: " + "received DATA frame length %uz, available window %uz", + h2c->state.length, h2c->recv_window); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR); + } + + h2c->recv_window -= h2c->state.length; + + if (h2c->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) { + + if (ngx_http_v2_send_window_update(h2c, 0, NGX_HTTP_V2_MAX_WINDOW + - h2c->recv_window) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->recv_window = NGX_HTTP_V2_MAX_WINDOW; + } + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node == NULL || node->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "unknown http2 stream"); + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + stream = node->stream; + + if (h2c->state.length > stream->recv_window) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client violated flow control for stream %ui: " + "received DATA frame length %uz, available window %uz", + node->id, h2c->state.length, stream->recv_window); + + if (ngx_http_v2_terminate_stream(h2c, stream, + NGX_HTTP_V2_FLOW_CTRL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + stream->recv_window -= h2c->state.length; + + if (stream->recv_window < NGX_HTTP_V2_MAX_WINDOW / 4) { + + if (ngx_http_v2_send_window_update(h2c, node->id, + NGX_HTTP_V2_MAX_WINDOW + - stream->recv_window) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + stream->recv_window = NGX_HTTP_V2_MAX_WINDOW; + } + + if (stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent DATA frame for half-closed stream %ui", + node->id); + + if (ngx_http_v2_terminate_stream(h2c, stream, + NGX_HTTP_V2_STREAM_CLOSED) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + h2c->state.stream = stream; + + return ngx_http_v2_state_read_data(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + ssize_t n; + ngx_buf_t *buf; + ngx_int_t rc; + ngx_temp_file_t *tf; + ngx_http_request_t *r; + ngx_http_v2_stream_t *stream; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + stream = h2c->state.stream; + + if (stream == NULL) { + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + if (stream->skip_data) { + stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "skipping http2 DATA frame, reason: %d", + stream->skip_data); + + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + size = end - pos; + + if (size > h2c->state.length) { + size = h2c->state.length; + } + + r = stream->request; + + if (r->request_body == NULL + && ngx_http_v2_init_request_body(r) != NGX_OK) + { + stream->skip_data = NGX_HTTP_V2_DATA_INTERNAL_ERROR; + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + rb = r->request_body; + tf = rb->temp_file; + buf = rb->buf; + + if (size) { + rb->rest += size; + + if (r->headers_in.content_length_n != -1 + && r->headers_in.content_length_n < rb->rest) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client intended to send body data " + "larger than declared"); + + stream->skip_data = NGX_HTTP_V2_DATA_ERROR; + goto error; + + } else { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->client_max_body_size + && clcf->client_max_body_size < rb->rest) + { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send " + "too large chunked body: %O bytes", rb->rest); + + stream->skip_data = NGX_HTTP_V2_DATA_ERROR; + goto error; + } + } + + h2c->state.length -= size; + + if (tf) { + buf->start = pos; + buf->pos = pos; + + pos += size; + + buf->end = pos; + buf->last = pos; + + n = ngx_write_chain_to_temp_file(tf, rb->bufs); + + /* TODO: n == 0 or not complete and level event */ + + if (n == NGX_ERROR) { + stream->skip_data = NGX_HTTP_V2_DATA_INTERNAL_ERROR; + goto error; + } + + tf->offset += n; + + } else { + buf->last = ngx_cpymem(buf->last, pos, size); + pos += size; + } + + r->request_length += size; + } + + if (h2c->state.length) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_read_data); + } + + if (h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) { + stream->in_closed = 1; + + if (r->headers_in.content_length_n < 0) { + r->headers_in.content_length_n = rb->rest; + + } else if (r->headers_in.content_length_n != rb->rest) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream: " + "only %O out of %O bytes of request body received", + rb->rest, r->headers_in.content_length_n); + + stream->skip_data = NGX_HTTP_V2_DATA_ERROR; + goto error; + } + + if (tf) { + ngx_memzero(buf, sizeof(ngx_buf_t)); + + buf->in_file = 1; + buf->file_last = tf->file.offset; + buf->file = &tf->file; + + rb->buf = NULL; + } + + if (rb->post_handler) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + } + + if (h2c->state.padding) { + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + return ngx_http_v2_state_complete(h2c, pos, end); + +error: + + if (rb->post_handler) { + + if (stream->skip_data == NGX_HTTP_V2_DATA_ERROR) { + rc = (r->headers_in.content_length_n == -1) + ? NGX_HTTP_REQUEST_ENTITY_TOO_LARGE : NGX_HTTP_BAD_REQUEST; + + } else { + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + ngx_http_finalize_request(r, rc); + } + + return ngx_http_v2_state_skip_padded(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + ngx_uint_t padded, priority, depend, dependency, excl, weight; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + + padded = h2c->state.flags & NGX_HTTP_V2_PADDED_FLAG; + priority = h2c->state.flags & NGX_HTTP_V2_PRIORITY_FLAG; + + size = 0; + + if (padded) { + size++; + } + + if (priority) { + size += sizeof(uint32_t) + 1; + } + + if (h2c->state.length < size) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent HEADERS frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (h2c->state.length == size) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent HEADERS frame with empty header block"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if ((size_t) (end - pos) < size) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_headers); + } + + h2c->state.length -= size; + + if (padded) { + h2c->state.padding = *pos++; + + if (h2c->state.padding > h2c->state.length) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent padded HEADERS frame " + "with incorrect length: %uz, padding: %uz", + h2c->state.length, h2c->state.padding); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + h2c->state.length -= h2c->state.padding; + } + + depend = 0; + excl = 0; + weight = 16; + + if (priority) { + dependency = ngx_http_v2_parse_uint32(pos); + + depend = dependency & 0x7fffffff; + excl = dependency >> 31; + weight = pos[4] + 1; + + pos += sizeof(uint32_t) + 1; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 HEADERS frame sid:%ui on %ui excl:%ui weight:%ui", + h2c->state.sid, depend, excl, weight); + + if (h2c->state.sid % 2 == 0 || h2c->state.sid <= h2c->last_sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent HEADERS frame with incorrect identifier " + "%ui, the last was %ui", h2c->state.sid, h2c->last_sid); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + h2c->last_sid = h2c->state.sid; + + if (depend == h2c->state.sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent HEADERS frame for stream %ui " + "with incorrect dependency", h2c->state.sid); + + if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, + NGX_HTTP_V2_PROTOCOL_ERROR) + != NGX_OK) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_skip_headers(h2c, pos, end); + } + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + h2c->state.header_limit = h2scf->max_header_size; + + if (h2c->processing >= h2scf->concurrent_streams) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "concurrent streams exceeded %ui", h2c->processing); + + if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, + NGX_HTTP_V2_REFUSED_STREAM) + != NGX_OK) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_skip_headers(h2c, pos, end); + } + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 1); + + if (node == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + if (node->parent) { + ngx_queue_remove(&node->reuse); + h2c->closed_nodes--; + } + + stream = ngx_http_v2_create_stream(h2c); + if (stream == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; + stream->node = node; + + node->stream = stream; + + h2c->state.stream = stream; + h2c->state.pool = stream->request->pool; + + if (priority || node->parent == NULL) { + node->weight = weight; + ngx_http_v2_set_dependency(h2c, node, depend, excl); + } + + return ngx_http_v2_state_header_block(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_header_block(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + u_char ch; + ngx_int_t value; + ngx_uint_t indexed, size_update, prefix; + + if (end - pos < 1) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_header_block); + } + + if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) + && h2c->state.length < NGX_HTTP_V2_INT_OCTETS) + { + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_header_block); + } + + size_update = 0; + indexed = 0; + + ch = *pos; + + if (ch >= (1 << 7)) { + /* indexed header field */ + indexed = 1; + prefix = ngx_http_v2_prefix(7); + + } else if (ch >= (1 << 6)) { + /* literal header field with incremental indexing */ + h2c->state.index = 1; + prefix = ngx_http_v2_prefix(6); + + } else if (ch >= (1 << 5)) { + /* dynamic table size update */ + size_update = 1; + prefix = ngx_http_v2_prefix(5); + + } else if (ch >= (1 << 4)) { + /* literal header field never indexed */ + prefix = ngx_http_v2_prefix(4); + + } else { + /* literal header field without indexing */ + prefix = ngx_http_v2_prefix(4); + } + + value = ngx_http_v2_parse_int(h2c, &pos, end, prefix); + + if (value < 0) { + if (value == NGX_AGAIN) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_header_block); + } + + if (value == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header block with too long %s value", + size_update ? "size update" : "header index"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header block with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (indexed) { + if (ngx_http_v2_get_indexed_header(h2c, value, 0) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + return ngx_http_v2_state_process_header(h2c, pos, end); + } + + if (size_update) { + if (ngx_http_v2_table_size(h2c, value) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + if (value == 0) { + h2c->state.parse_name = 1; + + } else if (ngx_http_v2_get_indexed_header(h2c, value, 1) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + h2c->state.parse_value = 1; + + return ngx_http_v2_state_field_len(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_field_len(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t alloc; + ngx_int_t len; + ngx_uint_t huff; + ngx_http_v2_srv_conf_t *h2scf; + + if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) + && h2c->state.length < NGX_HTTP_V2_INT_OCTETS) + { + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_field_len); + } + + if (h2c->state.length < 1) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header block with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < 1) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_field_len); + } + + huff = *pos >> 7; + len = ngx_http_v2_parse_int(h2c, &pos, end, ngx_http_v2_prefix(7)); + + if (len < 0) { + if (len == NGX_AGAIN) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_field_len); + } + + if (len == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header field with too long length value"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header block with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 hpack %s string length: %i", + huff ? "encoded" : "raw", len); + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + if ((size_t) len > h2scf->max_field_size) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client exceeded http2_max_field_size limit"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM); + } + + h2c->state.field_rest = len; + + if (h2c->state.stream == NULL && !h2c->state.index) { + return ngx_http_v2_state_field_skip(h2c, pos, end); + } + + alloc = (huff ? len * 8 / 5 : len) + 1; + + h2c->state.field_start = ngx_pnalloc(h2c->state.pool, alloc); + if (h2c->state.field_start == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->state.field_end = h2c->state.field_start; + + if (huff) { + return ngx_http_v2_state_field_huff(h2c, pos, end); + } + + return ngx_http_v2_state_field_raw(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_field_huff(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + if (size > h2c->state.field_rest) { + size = h2c->state.field_rest; + } + + if (size > h2c->state.length) { + size = h2c->state.length; + } + + h2c->state.length -= size; + h2c->state.field_rest -= size; + + if (ngx_http_v2_huff_decode(&h2c->state.field_state, pos, size, + &h2c->state.field_end, + h2c->state.field_rest == 0, + h2c->connection->log) + != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent invalid encoded header field"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_COMP_ERROR); + } + + pos += size; + + if (h2c->state.field_rest == 0) { + *h2c->state.field_end = '\0'; + return ngx_http_v2_state_process_header(h2c, pos, end); + } + + if (h2c->state.length) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_field_huff); + } + + if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header field with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_field_huff); +} + + +static u_char * +ngx_http_v2_state_field_raw(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + if (size > h2c->state.field_rest) { + size = h2c->state.field_rest; + } + + if (size > h2c->state.length) { + size = h2c->state.length; + } + + h2c->state.length -= size; + h2c->state.field_rest -= size; + + h2c->state.field_end = ngx_cpymem(h2c->state.field_end, pos, size); + + pos += size; + + if (h2c->state.field_rest == 0) { + *h2c->state.field_end = '\0'; + return ngx_http_v2_state_process_header(h2c, pos, end); + } + + if (h2c->state.length) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_field_raw); + } + + if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header field with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_field_raw); +} + + +static u_char * +ngx_http_v2_state_field_skip(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t size; + + size = end - pos; + + if (size > h2c->state.field_rest) { + size = h2c->state.field_rest; + } + + if (size > h2c->state.length) { + size = h2c->state.length; + } + + h2c->state.length -= size; + h2c->state.field_rest -= size; + + pos += size; + + if (h2c->state.field_rest == 0) { + return ngx_http_v2_state_process_header(h2c, pos, end); + } + + if (h2c->state.length) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_field_skip); + } + + if (h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent header field with incorrect length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_field_skip); +} + + +static u_char * +ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t len; + ngx_int_t rc; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_request_t *r; + ngx_http_v2_header_t *header; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + header = &h2c->state.header; + + if (h2c->state.parse_name) { + h2c->state.parse_name = 0; + + header->name.len = h2c->state.field_end - h2c->state.field_start; + header->name.data = h2c->state.field_start; + + return ngx_http_v2_state_field_len(h2c, pos, end); + } + + if (h2c->state.parse_value) { + h2c->state.parse_value = 0; + + header->value.len = h2c->state.field_end - h2c->state.field_start; + header->value.data = h2c->state.field_start; + } + + len = header->name.len + header->value.len; + + if (len > h2c->state.header_limit) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client exceeded http2_max_header_size limit"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM); + } + + h2c->state.header_limit -= len; + + if (h2c->state.index) { + if (ngx_http_v2_add_header(h2c, header) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->state.index = 0; + } + + if (h2c->state.stream == NULL) { + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + r = h2c->state.stream->request; + + /* TODO Optimization: validate headers while parsing. */ + if (ngx_http_v2_validate_header(r, header) != NGX_OK) { + if (ngx_http_v2_terminate_stream(h2c, h2c->state.stream, + NGX_HTTP_V2_PROTOCOL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + goto error; + } + + if (header->name.data[0] == ':') { + rc = ngx_http_v2_pseudo_header(r, header); + + if (rc == NGX_OK) { + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + if (rc == NGX_ABORT) { + goto error; + } + + if (rc == NGX_DECLINED) { + if (ngx_http_v2_terminate_stream(h2c, h2c->state.stream, + NGX_HTTP_V2_PROTOCOL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + goto error; + } + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", &header->name); + + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + } + + if (header->name.len == cookie.len + && ngx_memcmp(header->name.data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v2_cookie(r, header) != NGX_OK) { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_header_complete(h2c, pos, end); + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + h->key.len = header->name.len; + h->key.data = header->name.data; + + /* TODO Optimization: precalculate hash and handler for indexed headers. */ + h->hash = ngx_hash_key(h->key.data, h->key.len); + + h->value.len = header->value.len; + h->value.data = header->value.data; + + h->lowcase_key = h->key.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + goto error; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 http header: \"%V: %V\"", &h->key, &h->value); + + return ngx_http_v2_state_header_complete(h2c, pos, end); + +error: + + h2c->state.stream = NULL; + h2c->state.pool = NULL; + + return ngx_http_v2_state_header_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_header_complete(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_http_v2_stream_t *stream; + + if (h2c->state.length) { + h2c->state.handler = h2c->state.pool ? ngx_http_v2_state_header_block + : ngx_http_v2_state_skip_headers; + return pos; + } + + if (!(h2c->state.flags & NGX_HTTP_V2_END_HEADERS_FLAG)) { + return ngx_http_v2_handle_continuation(h2c, pos, end, + ngx_http_v2_state_header_complete); + } + + stream = h2c->state.stream; + + if (stream) { + ngx_http_v2_run_request(stream->request); + + } else if (h2c->state.pool) { + ngx_destroy_pool(h2c->state.pool); + } + + h2c->state.pool = NULL; + + if (h2c->state.padding) { + return ngx_http_v2_state_skip_padded(h2c, pos, end); + } + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_handle_continuation(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end, ngx_http_v2_handler_pt handler) +{ + u_char *p; + size_t len; + uint32_t head; + + len = h2c->state.length; + + if ((size_t) (end - pos) < len + NGX_HTTP_V2_FRAME_HEADER_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, handler); + } + + p = pos + len; + + head = ngx_http_v2_parse_uint32(p); + + if (ngx_http_v2_parse_type(head) != NGX_HTTP_V2_CONTINUATION_FRAME) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent inappropriate frame while CONTINUATION was expected"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + h2c->state.length += ngx_http_v2_parse_length(head); + h2c->state.flags |= p[4]; + + if (h2c->state.sid != ngx_http_v2_parse_sid(&p[5])) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent CONTINUATION frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + p = pos; + pos += NGX_HTTP_V2_FRAME_HEADER_SIZE; + + ngx_memcpy(pos, p, len); + + h2c->state.handler = handler; + return pos; +} + + +static u_char * +ngx_http_v2_state_priority(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_uint_t depend, dependency, excl, weight; + ngx_http_v2_node_t *node; + + if (h2c->state.length != NGX_HTTP_V2_PRIORITY_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PRIORITY frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_PRIORITY_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_priority); + } + + dependency = ngx_http_v2_parse_uint32(pos); + + depend = dependency & 0x7fffffff; + excl = dependency >> 31; + weight = pos[4] + 1; + + pos += NGX_HTTP_V2_PRIORITY_SIZE; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 PRIORITY frame sid:%ui on %ui excl:%ui weight:%ui", + h2c->state.sid, depend, excl, weight); + + if (h2c->state.sid == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PRIORITY frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + if (depend == h2c->state.sid) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PRIORITY frame for stream %ui " + "with incorrect dependency", h2c->state.sid); + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node && node->stream) { + if (ngx_http_v2_terminate_stream(h2c, node->stream, + NGX_HTTP_V2_PROTOCOL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + } else { + if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, + NGX_HTTP_V2_PROTOCOL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + } + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 1); + + if (node == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + node->weight = weight; + + if (node->stream == NULL) { + if (node->parent == NULL) { + h2c->closed_nodes++; + + } else { + ngx_queue_remove(&node->reuse); + } + + ngx_queue_insert_tail(&h2c->closed, &node->reuse); + } + + ngx_http_v2_set_dependency(h2c, node, depend, excl); + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_uint_t status; + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + + if (h2c->state.length != NGX_HTTP_V2_RST_STREAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent RST_STREAM frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_RST_STREAM_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_rst_stream); + } + + status = ngx_http_v2_parse_uint32(pos); + + pos += NGX_HTTP_V2_RST_STREAM_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 RST_STREAM frame, sid:%ui status:%ui", + h2c->state.sid, status); + + if (h2c->state.sid == 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent RST_STREAM frame with incorrect identifier"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); + } + + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node == NULL || node->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "unknown http2 stream"); + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + stream = node->stream; + + stream->in_closed = 1; + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + switch (status) { + + case NGX_HTTP_V2_CANCEL: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client canceled stream %ui", h2c->state.sid); + break; + + case NGX_HTTP_V2_INTERNAL_ERROR: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui due to internal error", + h2c->state.sid); + break; + + default: + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client terminated stream %ui with status %ui", + h2c->state.sid, status); + break; + } + + ev = fc->read; + ev->handler(ev); + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + if (h2c->state.flags == NGX_HTTP_V2_ACK_FLAG) { + + if (h2c->state.length != 0) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with the ACK flag " + "and nonzero length"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + /* TODO settings acknowledged */ + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + if (h2c->state.length % NGX_HTTP_V2_SETTINGS_PARAM_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + ngx_http_v2_send_settings(h2c, 1); + + return ngx_http_v2_state_settings_params(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_uint_t id, value; + + while (h2c->state.length) { + if (end - pos < NGX_HTTP_V2_SETTINGS_PARAM_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_settings_params); + } + + h2c->state.length -= NGX_HTTP_V2_SETTINGS_PARAM_SIZE; + + id = ngx_http_v2_parse_uint16(pos); + value = ngx_http_v2_parse_uint32(&pos[2]); + + switch (id) { + + case NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING: + + if (value > NGX_HTTP_V2_MAX_WINDOW) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with incorrect " + "INITIAL_WINDOW_SIZE value %ui", value); + + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_FLOW_CTRL_ERROR); + } + + if (ngx_http_v2_adjust_windows(h2c, value - h2c->init_window) + != NGX_OK) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->init_window = value; + break; + + case NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING: + if (value > NGX_HTTP_V2_MAX_FRAME_SIZE + || value < NGX_HTTP_V2_DEFAULT_FRAME_SIZE) + { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent SETTINGS frame with incorrect " + "MAX_FRAME_SIZE value %ui", value); + + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_PROTOCOL_ERROR); + } + + h2c->frame_size = value; + break; + + default: + break; + } + + pos += NGX_HTTP_V2_SETTINGS_PARAM_SIZE; + } + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_push_promise(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PUSH_PROMISE frame"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); +} + + +static u_char * +ngx_http_v2_state_ping(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) +{ + ngx_buf_t *buf; + ngx_http_v2_out_frame_t *frame; + + if (h2c->state.length != NGX_HTTP_V2_PING_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent PING frame with incorrect length %uz", + h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_PING_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_ping); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 PING frame, flags: %ui", h2c->state.flags); + + if (h2c->state.flags & NGX_HTTP_V2_ACK_FLAG) { + return ngx_http_v2_state_skip(h2c, pos, end); + } + + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_PING_SIZE, + NGX_HTTP_V2_PING_FRAME, + NGX_HTTP_V2_ACK_FLAG, 0); + if (frame == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + buf = frame->first->buf; + + buf->last = ngx_cpymem(buf->last, pos, NGX_HTTP_V2_PING_SIZE); + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return ngx_http_v2_state_complete(h2c, pos + NGX_HTTP_V2_PING_SIZE, end); +} + + +static u_char * +ngx_http_v2_state_goaway(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ +#if (NGX_DEBUG) + ngx_uint_t last_sid, error; +#endif + + if (h2c->state.length < NGX_HTTP_V2_GOAWAY_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent GOAWAY frame " + "with incorrect length %uz", h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_GOAWAY_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_goaway); + } + +#if (NGX_DEBUG) + h2c->state.length -= NGX_HTTP_V2_GOAWAY_SIZE; + + last_sid = ngx_http_v2_parse_sid(pos); + error = ngx_http_v2_parse_uint32(&pos[4]); + + pos += NGX_HTTP_V2_GOAWAY_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 GOAWAY frame: last sid %ui, error %ui", + last_sid, error); +#endif + + return ngx_http_v2_state_skip(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_window_update(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + size_t window; + ngx_event_t *wev; + ngx_queue_t *q; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + + if (h2c->state.length != NGX_HTTP_V2_WINDOW_UPDATE_SIZE) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent WINDOW_UPDATE frame " + "with incorrect length %uz", h2c->state.length); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); + } + + if (end - pos < NGX_HTTP_V2_WINDOW_UPDATE_SIZE) { + return ngx_http_v2_state_save(h2c, pos, end, + ngx_http_v2_state_window_update); + } + + window = ngx_http_v2_parse_window(pos); + + pos += NGX_HTTP_V2_WINDOW_UPDATE_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 WINDOW_UPDATE frame sid:%ui window:%uz", + h2c->state.sid, window); + + if (h2c->state.sid) { + node = ngx_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node == NULL || node->stream == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "unknown http2 stream"); + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + stream = node->stream; + + if (window > (size_t) (NGX_HTTP_V2_MAX_WINDOW - stream->send_window)) { + + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client violated flow control for stream %ui: " + "received WINDOW_UPDATE frame " + "with window increment %uz " + "not allowed for window %z", + h2c->state.sid, window, stream->send_window); + + if (ngx_http_v2_terminate_stream(h2c, stream, + NGX_HTTP_V2_FLOW_CTRL_ERROR) + == NGX_ERROR) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + stream->send_window += window; + + if (stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + + return ngx_http_v2_state_complete(h2c, pos, end); + } + + if (window > NGX_HTTP_V2_MAX_WINDOW - h2c->send_window) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client violated connection flow control: " + "received WINDOW_UPDATE frame " + "with window increment %uz " + "not allowed for window %uz", + window, h2c->send_window); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_FLOW_CTRL_ERROR); + } + + h2c->send_window += window; + + while (!ngx_queue_empty(&h2c->waiting)) { + q = ngx_queue_head(&h2c->waiting); + + ngx_queue_remove(q); + + stream = ngx_queue_data(q, ngx_http_v2_stream_t, queue); + + stream->handled = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + + if (h2c->send_window == 0) { + break; + } + } + } + + return ngx_http_v2_state_complete(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_continuation(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent unexpected CONTINUATION frame"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); +} + + +static u_char * +ngx_http_v2_state_complete(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame complete pos:%p end:%p", pos, end); + + if (pos > end) { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "receive buffer overrun"); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->state.stream = NULL; + h2c->state.handler = ngx_http_v2_state_head; + + return pos; +} + + +static u_char * +ngx_http_v2_state_skip_padded(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + h2c->state.length += h2c->state.padding; + h2c->state.padding = 0; + + return ngx_http_v2_state_skip(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_skip(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) +{ + size_t size; + + size = end - pos; + + if (size < h2c->state.length) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame skip %uz of %uz", size, h2c->state.length); + + h2c->state.length -= size; + return ngx_http_v2_state_save(h2c, end, end, ngx_http_v2_state_skip); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame skip %uz", h2c->state.length); + + return ngx_http_v2_state_complete(h2c, pos + h2c->state.length, end); +} + + +static u_char * +ngx_http_v2_state_skip_headers(ngx_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + h2c->state.pool = ngx_create_pool(1024, h2c->connection->log); + if (h2c->state.pool == NULL) { + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + return ngx_http_v2_state_header_block(h2c, pos, end); +} + + +static u_char * +ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end, + ngx_http_v2_handler_pt handler) +{ + size_t size; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 frame state save pos:%p end:%p handler:%p", + pos, end, handler); + + size = end - pos; + + if (size > NGX_HTTP_V2_STATE_BUFFER_SIZE) { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "state buffer overflow: %uz bytes required", size); + + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + } + + ngx_memcpy(h2c->state.buffer, pos, NGX_HTTP_V2_STATE_BUFFER_SIZE); + + h2c->state.buffer_used = size; + h2c->state.handler = handler; + h2c->state.incomplete = 1; + + return end; +} + + +static u_char * +ngx_http_v2_connection_error(ngx_http_v2_connection_t *h2c, + ngx_uint_t err) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 state connection error"); + + if (err == NGX_HTTP_V2_INTERNAL_ERROR) { + ngx_debug_point(); + } + + ngx_http_v2_finalize_connection(h2c, err); + + return NULL; +} + + +static ngx_int_t +ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end, + ngx_uint_t prefix) +{ + u_char *start, *p; + ngx_uint_t value, octet, shift; + + start = *pos; + p = start; + + value = *p++ & prefix; + + if (value != prefix) { + if (h2c->state.length == 0) { + return NGX_ERROR; + } + + h2c->state.length--; + + *pos = p; + return value; + } + + if (end - start > NGX_HTTP_V2_INT_OCTETS) { + end = start + NGX_HTTP_V2_INT_OCTETS; + } + + for (shift = 0; p != end; shift += 7) { + octet = *p++; + + value += (octet & 0x7f) << shift; + + if (octet < 128) { + if ((size_t) (p - start) > h2c->state.length) { + return NGX_ERROR; + } + + h2c->state.length -= p - start; + + *pos = p; + return value; + } + } + + if ((size_t) (end - start) >= h2c->state.length) { + return NGX_ERROR; + } + + if (end == start + NGX_HTTP_V2_INT_OCTETS) { + return NGX_DECLINED; + } + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c, ngx_uint_t ack) +{ + size_t len; + ngx_buf_t *buf; + ngx_chain_t *cl; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_v2_out_frame_t *frame; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 send SETTINGS frame"); + + frame = ngx_palloc(h2c->pool, sizeof(ngx_http_v2_out_frame_t)); + if (frame == NULL) { + return NGX_ERROR; + } + + cl = ngx_alloc_chain_link(h2c->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + len = ack ? 0 : (sizeof(uint16_t) + sizeof(uint32_t)) * 3; + + buf = ngx_create_temp_buf(h2c->pool, NGX_HTTP_V2_FRAME_HEADER_SIZE + len); + if (buf == NULL) { + return NGX_ERROR; + } + + buf->last_buf = 1; + + cl->buf = buf; + cl->next = NULL; + + frame->first = cl; + frame->last = cl; + frame->handler = ngx_http_v2_settings_frame_handler; + frame->stream = NULL; +#if (NGX_DEBUG) + frame->length = len; +#endif + frame->blocked = 0; + + buf->last = ngx_http_v2_write_len_and_type(buf->last, len, + NGX_HTTP_V2_SETTINGS_FRAME); + + *buf->last++ = ack ? NGX_HTTP_V2_ACK_FLAG : NGX_HTTP_V2_NO_FLAG; + + buf->last = ngx_http_v2_write_sid(buf->last, 0); + + if (!ack) { + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + buf->last = ngx_http_v2_write_uint16(buf->last, + NGX_HTTP_V2_MAX_STREAMS_SETTING); + buf->last = ngx_http_v2_write_uint32(buf->last, + h2scf->concurrent_streams); + + buf->last = ngx_http_v2_write_uint16(buf->last, + NGX_HTTP_V2_INIT_WINDOW_SIZE_SETTING); + buf->last = ngx_http_v2_write_uint32(buf->last, + NGX_HTTP_V2_MAX_WINDOW); + + buf->last = ngx_http_v2_write_uint16(buf->last, + NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING); + buf->last = ngx_http_v2_write_uint32(buf->last, + NGX_HTTP_V2_MAX_FRAME_SIZE); + } + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_settings_frame_handler(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + ngx_free_chain(h2c->pool, frame->first); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_send_window_update(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, + size_t window) +{ + ngx_buf_t *buf; + ngx_http_v2_out_frame_t *frame; + + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_WINDOW_UPDATE_SIZE, + NGX_HTTP_V2_WINDOW_UPDATE_FRAME, + NGX_HTTP_V2_NO_FLAG, sid); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + buf->last = ngx_http_v2_write_uint32(buf->last, window); + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_send_rst_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, + ngx_uint_t status) +{ + ngx_buf_t *buf; + ngx_http_v2_out_frame_t *frame; + + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_RST_STREAM_SIZE, + NGX_HTTP_V2_RST_STREAM_FRAME, + NGX_HTTP_V2_NO_FLAG, sid); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + buf->last = ngx_http_v2_write_uint32(buf->last, status); + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_send_goaway(ngx_http_v2_connection_t *h2c, ngx_uint_t status) +{ + ngx_buf_t *buf; + ngx_http_v2_out_frame_t *frame; + + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_GOAWAY_SIZE, + NGX_HTTP_V2_GOAWAY_FRAME, + NGX_HTTP_V2_NO_FLAG, 0); + if (frame == NULL) { + return NGX_ERROR; + } + + buf = frame->first->buf; + + buf->last = ngx_http_v2_write_sid(buf->last, h2c->last_sid); + buf->last = ngx_http_v2_write_uint32(buf->last, status); + + ngx_http_v2_queue_blocked_frame(h2c, frame); + + return NGX_OK; +} + + +static ngx_http_v2_out_frame_t * +ngx_http_v2_get_frame(ngx_http_v2_connection_t *h2c, size_t length, + ngx_uint_t type, u_char flags, ngx_uint_t sid) +{ + ngx_buf_t *buf; + ngx_pool_t *pool; + ngx_http_v2_out_frame_t *frame; + + frame = h2c->free_frames; + + if (frame) { + h2c->free_frames = frame->next; + + buf = frame->first->buf; + buf->pos = buf->start; + + frame->blocked = 0; + + } else { + pool = h2c->pool ? h2c->pool : h2c->connection->pool; + + frame = ngx_pcalloc(pool, sizeof(ngx_http_v2_out_frame_t)); + if (frame == NULL) { + return NULL; + } + + frame->first = ngx_alloc_chain_link(pool); + if (frame->first == NULL) { + return NULL; + } + + buf = ngx_create_temp_buf(pool, NGX_HTTP_V2_FRAME_BUFFER_SIZE); + if (buf == NULL) { + return NULL; + } + + buf->last_buf = 1; + + frame->first->buf = buf; + frame->last = frame->first; + + frame->handler = ngx_http_v2_frame_handler; + } + +#if (NGX_DEBUG) + if (length > NGX_HTTP_V2_FRAME_BUFFER_SIZE - NGX_HTTP_V2_FRAME_HEADER_SIZE) + { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "requested control frame is too large: %uz", length); + return NULL; + } + + frame->length = length; +#endif + + buf->last = ngx_http_v2_write_len_and_type(buf->pos, length, type); + + *buf->last++ = flags; + + buf->last = ngx_http_v2_write_sid(buf->last, sid); + + return frame; +} + + +static ngx_int_t +ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, + ngx_http_v2_out_frame_t *frame) +{ + ngx_buf_t *buf; + + buf = frame->first->buf; + + if (buf->pos != buf->last) { + return NGX_AGAIN; + } + + frame->next = h2c->free_frames; + h2c->free_frames = frame; + + return NGX_OK; +} + + +static ngx_http_v2_stream_t * +ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c) +{ + ngx_log_t *log; + ngx_event_t *rev, *wev; + ngx_connection_t *fc; + ngx_http_log_ctx_t *ctx; + ngx_http_request_t *r; + ngx_http_v2_stream_t *stream; + ngx_http_core_srv_conf_t *cscf; + + fc = h2c->free_fake_connections; + + if (fc) { + h2c->free_fake_connections = fc->data; + + rev = fc->read; + wev = fc->write; + log = fc->log; + ctx = log->data; + + } else { + fc = ngx_palloc(h2c->pool, sizeof(ngx_connection_t)); + if (fc == NULL) { + return NULL; + } + + rev = ngx_palloc(h2c->pool, sizeof(ngx_event_t)); + if (rev == NULL) { + return NULL; + } + + wev = ngx_palloc(h2c->pool, sizeof(ngx_event_t)); + if (wev == NULL) { + return NULL; + } + + log = ngx_palloc(h2c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + return NULL; + } + + ctx = ngx_palloc(h2c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NULL; + } + + ctx->connection = fc; + ctx->request = NULL; + ctx->current_request = NULL; + } + + ngx_memcpy(log, h2c->connection->log, sizeof(ngx_log_t)); + + log->data = ctx; + + ngx_memzero(rev, sizeof(ngx_event_t)); + + rev->data = fc; + rev->ready = 1; + rev->handler = ngx_http_v2_close_stream_handler; + rev->log = log; + + ngx_memcpy(wev, rev, sizeof(ngx_event_t)); + + wev->write = 1; + + ngx_memcpy(fc, h2c->connection, sizeof(ngx_connection_t)); + + fc->data = h2c->http_connection; + fc->read = rev; + fc->write = wev; + fc->sent = 0; + fc->log = log; + fc->buffered = 0; + fc->sndlowat = 1; + fc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + r = ngx_http_create_request(fc); + if (r == NULL) { + return NULL; + } + + ngx_str_set(&r->http_protocol, "HTTP/2.0"); + + r->http_version = NGX_HTTP_VERSION_20; + r->valid_location = 1; + + fc->data = r; + h2c->connection->requests++; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + stream = ngx_pcalloc(r->pool, sizeof(ngx_http_v2_stream_t)); + if (stream == NULL) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NULL; + } + + r->stream = stream; + + stream->request = r; + stream->connection = h2c; + + stream->send_window = h2c->init_window; + stream->recv_window = NGX_HTTP_V2_MAX_WINDOW; + + h2c->processing++; + + return stream; +} + + +static ngx_http_v2_node_t * +ngx_http_v2_get_node_by_id(ngx_http_v2_connection_t *h2c, ngx_uint_t sid, + ngx_uint_t alloc) +{ + ngx_uint_t index; + ngx_http_v2_node_t *node; + ngx_http_v2_srv_conf_t *h2scf; + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + index = ngx_http_v2_index(h2scf, sid); + + for (node = h2c->streams_index[index]; node; node = node->index) { + + if (node->id == sid) { + return node; + } + } + + if (!alloc) { + return NULL; + } + + if (h2c->closed_nodes < 32) { + node = ngx_pcalloc(h2c->connection->pool, sizeof(ngx_http_v2_node_t)); + if (node == NULL) { + return NULL; + } + + } else { + node = ngx_http_v2_get_closed_node(h2c); + } + + node->id = sid; + + ngx_queue_init(&node->children); + + node->index = h2c->streams_index[index]; + h2c->streams_index[index] = node; + + return node; +} + + +static ngx_http_v2_node_t * +ngx_http_v2_get_closed_node(ngx_http_v2_connection_t *h2c) +{ + ngx_uint_t weight; + ngx_queue_t *q, *children; + ngx_http_v2_node_t *node, **next, *n, *parent, *child; + ngx_http_v2_srv_conf_t *h2scf; + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + h2c->closed_nodes--; + + q = ngx_queue_head(&h2c->closed); + + ngx_queue_remove(q); + + node = ngx_queue_data(q, ngx_http_v2_node_t, reuse); + + next = &h2c->streams_index[ngx_http_v2_index(h2scf, node->id)]; + + for ( ;; ) { + n = *next; + + if (n == node) { + *next = n->index; + break; + } + + next = &n->index; + } + + ngx_queue_remove(&node->queue); + + weight = 0; + + for (q = ngx_queue_head(&node->children); + q != ngx_queue_sentinel(&node->children); + q = ngx_queue_next(q)) + { + child = ngx_queue_data(q, ngx_http_v2_node_t, queue); + weight += child->weight; + } + + parent = node->parent; + + for (q = ngx_queue_head(&node->children); + q != ngx_queue_sentinel(&node->children); + q = ngx_queue_next(q)) + { + child = ngx_queue_data(q, ngx_http_v2_node_t, queue); + child->parent = parent; + child->weight = node->weight * child->weight / weight; + + if (child->weight == 0) { + child->weight = 1; + } + } + + if (parent == NGX_HTTP_V2_ROOT) { + node->rank = 0; + node->rel_weight = 1.0; + + children = &h2c->dependencies; + + } else { + node->rank = parent->rank; + node->rel_weight = parent->rel_weight; + + children = &parent->children; + } + + ngx_http_v2_node_children_update(node); + ngx_queue_add(children, &node->children); + + ngx_memzero(node, sizeof(ngx_http_v2_node_t)); + + return node; +} + + +static ngx_int_t +ngx_http_v2_validate_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + if (header->name.len == 0) { + return NGX_ERROR; + } + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (header->name.data[0] == ':'); i != header->name.len; i++) { + ch = header->name.data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + switch (ch) { + case '\0': + case LF: + case CR: + case ':': + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + &header->name); + + return NGX_ERROR; + } + + if (ch >= 'A' && ch <= 'Z') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", + &header->name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != header->value.len; i++) { + ch = header->value.data[i]; + + switch (ch) { + case '\0': + case LF: + case CR: + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", + &header->name, &header->value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_pseudo_header(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + header->name.len--; + header->name.data++; + + switch (header->name.len) { + case 4: + if (ngx_memcmp(header->name.data, "path", sizeof("path") - 1) + == 0) + { + return ngx_http_v2_parse_path(r, header); + } + + break; + + case 6: + if (ngx_memcmp(header->name.data, "method", sizeof("method") - 1) + == 0) + { + return ngx_http_v2_parse_method(r, header); + } + + if (ngx_memcmp(header->name.data, "scheme", sizeof("scheme") - 1) + == 0) + { + return ngx_http_v2_parse_scheme(r, header); + } + + break; + + case 9: + if (ngx_memcmp(header->name.data, "authority", sizeof("authority") - 1) + == 0) + { + return ngx_http_v2_parse_authority(r, header); + } + + break; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo header \"%V\"", + &header->name); + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_http_v2_parse_path(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + if (r->unparsed_uri.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :path header"); + + return NGX_DECLINED; + } + + if (header->value.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :path header"); + + return NGX_DECLINED; + } + + r->uri_start = header->value.data; + r->uri_end = header->value.data + header->value.len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid :path header: \"%V\"", + &header->value); + + return NGX_DECLINED; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_request_uri() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_parse_method(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + size_t k, len; + ngx_uint_t n; + const u_char *p, *m; + + /* + * This array takes less than 256 sequential bytes, + * and if typical CPU cache line size is 64 bytes, + * it is prefetched for 4 load operations. + */ + static const struct { + u_char len; + const u_char method[11]; + uint32_t value; + } tests[] = { + { 3, "GET", NGX_HTTP_GET }, + { 4, "POST", NGX_HTTP_POST }, + { 4, "HEAD", NGX_HTTP_HEAD }, + { 7, "OPTIONS", NGX_HTTP_OPTIONS }, + { 8, "PROPFIND", NGX_HTTP_PROPFIND }, + { 3, "PUT", NGX_HTTP_PUT }, + { 5, "MKCOL", NGX_HTTP_MKCOL }, + { 6, "DELETE", NGX_HTTP_DELETE }, + { 4, "COPY", NGX_HTTP_COPY }, + { 4, "MOVE", NGX_HTTP_MOVE }, + { 9, "PROPPATCH", NGX_HTTP_PROPPATCH }, + { 4, "LOCK", NGX_HTTP_LOCK }, + { 6, "UNLOCK", NGX_HTTP_UNLOCK }, + { 5, "PATCH", NGX_HTTP_PATCH }, + { 5, "TRACE", NGX_HTTP_TRACE } + }, *test; + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :method header"); + + return NGX_DECLINED; + } + + if (header->value.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :method header"); + + return NGX_DECLINED; + } + + r->method_name.len = header->value.len; + r->method_name.data = header->value.data; + + len = r->method_name.len; + n = sizeof(tests) / sizeof(tests[0]); + test = tests; + + do { + if (len == test->len) { + p = r->method_name.data; + m = test->method; + k = len; + + do { + if (*p++ != *m++) { + goto next; + } + } while (--k); + + r->method = test->value; + return NGX_OK; + } + + next: + test++; + + } while (--n); + + p = r->method_name.data; + + do { + if ((*p < 'A' || *p > 'Z') && *p != '_') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", + &r->method_name); + + return NGX_DECLINED; + } + + p++; + + } while (--len); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + if (r->schema_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate :schema header"); + + return NGX_DECLINED; + } + + if (header->value.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty :schema header"); + + return NGX_DECLINED; + } + + r->schema_start = header->value.data; + r->schema_end = header->value.data + header->value.len; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t host = ngx_string("host"); + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash_key(host.data, host.len); + + h->key.len = host.len; + h->key.data = host.data; + + h->value.len = header->value.len; + h->value.data = header->value.data; + + h->lowcase_key = host.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_host() + */ + return NGX_ABORT; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_construct_request_line(ngx_http_request_t *r) +{ + u_char *p; + + static const u_char ending[] = " HTTP/2.0"; + + if (r->method_name.len == 0 + || r->unparsed_uri.len == 0) + { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + r->request_line.len = r->method_name.len + 1 + + r->unparsed_uri.len + + sizeof(ending) - 1; + + p = ngx_pnalloc(r->pool, r->request_line.len + 1); + if (p == NULL) { + ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + + *p++ = ' '; + + p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len); + + ngx_memcpy(p, ending, sizeof(ending)); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 http request line: \"%V\"", &r->request_line); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->stream->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->stream->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + val->len = header->value.len; + val->data = header->value.data; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v2_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->stream->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash_key(cookie.data, cookie.len); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_v2_close_stream(r->stream, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v2_run_request(ngx_http_request_t *r) +{ + if (ngx_http_v2_construct_request_line(r) != NGX_OK) { + return; + } + + if (ngx_http_v2_construct_cookie_header(r) != NGX_OK) { + return; + } + + r->http_state = NGX_HTTP_PROCESS_REQUEST_STATE; + + if (ngx_http_process_request_header(r) != NGX_OK) { + return; + } + + if (r->headers_in.content_length_n > 0 && r->stream->in_closed) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + + r->stream->skip_data = NGX_HTTP_V2_DATA_ERROR; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; + } + + ngx_http_process_request(r); +} + + +static ngx_int_t +ngx_http_v2_init_request_body(ngx_http_request_t *r) +{ + ngx_buf_t *buf; + ngx_temp_file_t *tf; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t)); + if (rb == NULL) { + return NGX_ERROR; + } + + r->request_body = rb; + + if (r->stream->in_closed) { + return NGX_OK; + } + + rb->rest = r->headers_in.content_length_n; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->request_body_in_file_only + || rb->rest > (off_t) clcf->client_body_buffer_size + || rb->rest < 0) + { + tf = ngx_pcalloc(r->pool, sizeof(ngx_temp_file_t)); + if (tf == NULL) { + return NGX_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 (r->stream->in_closed + && ngx_create_temp_file(&tf->file, tf->path, tf->pool, + tf->persistent, tf->clean, tf->access) + != NGX_OK) + { + return NGX_ERROR; + } + + buf = ngx_calloc_buf(r->pool); + if (buf == NULL) { + return NGX_ERROR; + } + + } else { + + if (rb->rest == 0) { + return NGX_OK; + } + + buf = ngx_create_temp_buf(r->pool, (size_t) rb->rest); + if (buf == NULL) { + return NGX_ERROR; + } + } + + rb->buf = buf; + + rb->bufs = ngx_alloc_chain_link(r->pool); + if (rb->bufs == NULL) { + return NGX_ERROR; + } + + rb->bufs->buf = buf; + rb->bufs->next = NULL; + + rb->rest = 0; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v2_read_request_body(ngx_http_request_t *r, + ngx_http_client_body_handler_pt post_handler) +{ + ngx_http_v2_stream_t *stream; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 read request body"); + + stream = r->stream; + + switch (stream->skip_data) { + + case NGX_HTTP_V2_DATA_DISCARD: + post_handler(r); + return NGX_OK; + + case NGX_HTTP_V2_DATA_ERROR: + if (r->headers_in.content_length_n == -1) { + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } else { + return NGX_HTTP_BAD_REQUEST; + } + + case NGX_HTTP_V2_DATA_INTERNAL_ERROR: + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (!r->request_body && ngx_http_v2_init_request_body(r) != NGX_OK) { + stream->skip_data = NGX_HTTP_V2_DATA_INTERNAL_ERROR; + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (stream->in_closed) { + post_handler(r); + return NGX_OK; + } + + r->request_body->post_handler = post_handler; + + r->read_event_handler = ngx_http_test_reading; + r->write_event_handler = ngx_http_request_empty_handler; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_http_v2_terminate_stream(ngx_http_v2_connection_t *h2c, + ngx_http_v2_stream_t *stream, ngx_uint_t status) +{ + ngx_event_t *rev; + ngx_connection_t *fc; + + if (ngx_http_v2_send_rst_stream(h2c, stream->node->id, status) + == NGX_ERROR) + { + return NGX_ERROR; + } + + stream->out_closed = 1; + + fc = stream->request->connection; + fc->error = 1; + + rev = fc->read; + rev->handler(rev); + + return NGX_OK; +} + + +void +ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) +{ + ngx_event_t *ev; + ngx_connection_t *fc; + ngx_http_v2_node_t *node; + ngx_http_v2_connection_t *h2c; + + h2c = stream->connection; + node = stream->node; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 close stream %ui, queued %ui, processing %ui", + node->id, stream->queued, h2c->processing); + + fc = stream->request->connection; + + if (stream->queued) { + fc->write->handler = ngx_http_v2_close_stream_handler; + return; + } + + if (!stream->out_closed) { + if (ngx_http_v2_send_rst_stream(h2c, node->id, + NGX_HTTP_V2_INTERNAL_ERROR) + != NGX_OK) + { + h2c->connection->error = 1; + } + } + + node->stream = NULL; + + ngx_queue_insert_tail(&h2c->closed, &node->reuse); + h2c->closed_nodes++; + + ngx_http_free_request(stream->request, rc); + + ev = fc->read; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "fake read event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + ev = fc->write; + + if (ev->active || ev->disabled) { + ngx_log_error(NGX_LOG_ALERT, h2c->connection->log, 0, + "fake write event was activated"); + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + + if (ev->posted) { + ngx_delete_posted_event(ev); + } + + fc->data = h2c->free_fake_connections; + h2c->free_fake_connections = fc; + + h2c->processing--; + + if (h2c->processing || h2c->blocked) { + return; + } + + ev = h2c->connection->read; + + ev->handler = ngx_http_v2_handle_connection_handler; + ngx_post_event(ev, &ngx_posted_events); +} + + +static void +ngx_http_v2_close_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *fc; + ngx_http_request_t *r; + + fc = ev->data; + r = fc->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 close stream handler"); + + ngx_http_v2_close_stream(r->stream, 0); +} + + +static void +ngx_http_v2_handle_connection_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + + rev->handler = ngx_http_v2_read_handler; + + if (rev->ready) { + ngx_http_v2_read_handler(rev); + return; + } + + c = rev->data; + + ngx_http_v2_handle_connection(c->data); +} + + +static void +ngx_http_v2_idle_handler(ngx_event_t *rev) +{ + ngx_connection_t *c; + ngx_http_v2_srv_conf_t *h2scf; + ngx_http_v2_connection_t *h2c; + + c = rev->data; + h2c = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 idle handler"); + + if (rev->timedout || c->close) { + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); + return; + } + +#if (NGX_HAVE_KQUEUE) + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + if (rev->pending_eof) { + c->log->handler = NULL; + ngx_log_error(NGX_LOG_INFO, c->log, rev->kq_errno, + "kevent() reported that client %V closed " + "idle connection", &c->addr_text); +#if (NGX_HTTP_SSL) + if (c->ssl) { + c->ssl->no_send_shutdown = 1; + } +#endif + ngx_http_close_connection(c); + return; + } + } + +#endif + + c->destroyed = 0; + c->idle = 0; + ngx_reusable_connection(c, 0); + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); + if (h2c->pool == NULL) { + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_INTERNAL_ERROR); + return; + } + + c->write->handler = ngx_http_v2_write_handler; + + rev->handler = ngx_http_v2_read_handler; + ngx_http_v2_read_handler(rev); +} + + +static void +ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, + ngx_uint_t status) +{ + ngx_uint_t i, size; + ngx_event_t *ev; + ngx_connection_t *c, *fc; + ngx_http_request_t *r; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + + c = h2c->connection; + + if (h2c->state.stream) { + h2c->state.stream->out_closed = 1; + h2c->state.pool = NULL; + ngx_http_v2_close_stream(h2c->state.stream, NGX_HTTP_BAD_REQUEST); + } + + h2c->blocked = 1; + + if (!c->error && ngx_http_v2_send_goaway(h2c, status) != NGX_ERROR) { + (void) ngx_http_v2_send_output_queue(h2c); + } + + if (!h2c->processing) { + ngx_http_close_connection(c); + return; + } + + c->error = 1; + c->read->handler = ngx_http_empty_handler; + c->write->handler = ngx_http_empty_handler; + + h2c->last_out = NULL; + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + size = ngx_http_v2_index_size(h2scf); + + for (i = 0; i < size; i++) { + + for (node = h2c->streams_index[i]; node; node = node->index) { + stream = node->stream; + + if (stream == NULL) { + continue; + } + + stream->handled = 0; + + r = stream->request; + fc = r->connection; + + fc->error = 1; + + if (stream->queued) { + stream->queued = 0; + + ev = fc->write; + ev->delayed = 0; + + } else { + ev = fc->read; + } + + ev->eof = 1; + ev->handler(ev); + } + } + + h2c->blocked = 0; + + if (h2c->processing) { + return; + } + + ngx_http_close_connection(c); +} + + +static ngx_int_t +ngx_http_v2_adjust_windows(ngx_http_v2_connection_t *h2c, ssize_t delta) +{ + ngx_uint_t i, size; + ngx_event_t *wev; + ngx_http_v2_node_t *node; + ngx_http_v2_stream_t *stream; + ngx_http_v2_srv_conf_t *h2scf; + + h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + size = ngx_http_v2_index_size(h2scf); + + for (i = 0; i < size; i++) { + + for (node = h2c->streams_index[i]; node; node = node->index) { + stream = node->stream; + + if (stream == NULL) { + continue; + } + + if (delta > 0 + && stream->send_window + > (ssize_t) (NGX_HTTP_V2_MAX_WINDOW - delta)) + { + if (ngx_http_v2_terminate_stream(h2c, stream, + NGX_HTTP_V2_FLOW_CTRL_ERROR) + == NGX_ERROR) + { + return NGX_ERROR; + } + + continue; + } + + stream->send_window += delta; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2:%ui adjusted window: %z", + node->id, stream->send_window); + + if (stream->send_window > 0 && stream->exhausted) { + stream->exhausted = 0; + + wev = stream->request->connection->write; + + if (!wev->timer_set) { + wev->delayed = 0; + wev->handler(wev); + } + } + } + } + + return NGX_OK; +} + + +static void +ngx_http_v2_set_dependency(ngx_http_v2_connection_t *h2c, + ngx_http_v2_node_t *node, ngx_uint_t depend, ngx_uint_t exclusive) +{ + ngx_queue_t *children, *q; + ngx_http_v2_node_t *parent, *child, *next; + + parent = depend ? ngx_http_v2_get_node_by_id(h2c, depend, 0) : NULL; + + if (parent == NULL) { + parent = NGX_HTTP_V2_ROOT; + + if (depend != 0) { + exclusive = 0; + } + + node->rank = 1; + node->rel_weight = (1.0 / 256) * node->weight; + + children = &h2c->dependencies; + + } else { + if (node->parent != NULL) { + + for (next = parent->parent; + next != NGX_HTTP_V2_ROOT && next->rank >= node->rank; + next = next->parent) + { + if (next != node) { + continue; + } + + ngx_queue_remove(&parent->queue); + ngx_queue_insert_after(&node->queue, &parent->queue); + + parent->parent = node->parent; + + if (node->parent == NGX_HTTP_V2_ROOT) { + parent->rank = 1; + parent->rel_weight = (1.0 / 256) * parent->weight; + + } else { + parent->rank = node->parent->rank + 1; + parent->rel_weight = (node->parent->rel_weight / 256) + * parent->weight; + } + + if (!exclusive) { + ngx_http_v2_node_children_update(parent); + } + + break; + } + } + + node->rank = parent->rank + 1; + node->rel_weight = (parent->rel_weight / 256) * node->weight; + + if (parent->stream == NULL) { + ngx_queue_remove(&parent->reuse); + ngx_queue_insert_tail(&h2c->closed, &parent->reuse); + } + + children = &parent->children; + } + + if (exclusive) { + for (q = ngx_queue_head(children); + q != ngx_queue_sentinel(children); + q = ngx_queue_next(q)) + { + child = ngx_queue_data(q, ngx_http_v2_node_t, queue); + child->parent = node; + } + + ngx_queue_add(&node->children, children); + ngx_queue_init(children); + } + + if (node->parent != NULL) { + ngx_queue_remove(&node->queue); + } + + ngx_queue_insert_tail(children, &node->queue); + + node->parent = parent; + + ngx_http_v2_node_children_update(node); +} + + +static void +ngx_http_v2_node_children_update(ngx_http_v2_node_t *node) +{ + ngx_queue_t *q; + ngx_http_v2_node_t *child; + + for (q = ngx_queue_head(&node->children); + q != ngx_queue_sentinel(&node->children); + q = ngx_queue_next(q)) + { + child = ngx_queue_data(q, ngx_http_v2_node_t, queue); + + child->rank = node->rank + 1; + child->rel_weight = (node->rel_weight / 256) * child->weight; + + ngx_http_v2_node_children_update(child); + } +} + + +static void +ngx_http_v2_pool_cleanup(void *data) +{ + ngx_http_v2_connection_t *h2c = data; + + if (h2c->state.pool) { + ngx_destroy_pool(h2c->state.pool); + } + + if (h2c->pool) { + ngx_destroy_pool(h2c->pool); + } +} From 094158a4f6a79b24bdbcee98e9cb07263070eb6f Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 15:16:09 +0000 Subject: [PATCH 2/4] commit patch 25138482 --- src/http/ngx_http_special_response.c | 6 + src/http/ngx_http_special_response.c.orig | 789 ++++++++++++++++++++++ 2 files changed, 795 insertions(+) create mode 100644 src/http/ngx_http_special_response.c.orig diff --git a/src/http/ngx_http_special_response.c b/src/http/ngx_http_special_response.c index 2771e588c6..28a39cea6f 100644 --- a/src/http/ngx_http_special_response.c +++ b/src/http/ngx_http_special_response.c @@ -571,6 +571,12 @@ ngx_http_send_error_page(ngx_http_request_t *r, ngx_http_err_page_t *err_page) return ngx_http_named_location(r, &uri); } + r->expect_tested = 1; + + if (ngx_http_discard_request_body(r) != NGX_OK) { + r->keepalive = 0; + } + location = ngx_list_push(&r->headers_out.headers); if (location == NULL) { diff --git a/src/http/ngx_http_special_response.c.orig b/src/http/ngx_http_special_response.c.orig new file mode 100644 index 0000000000..2771e588c6 --- /dev/null +++ b/src/http/ngx_http_special_response.c.orig @@ -0,0 +1,789 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include + + +static ngx_int_t ngx_http_send_error_page(ngx_http_request_t *r, + ngx_http_err_page_t *err_page); +static ngx_int_t ngx_http_send_special_response(ngx_http_request_t *r, + ngx_http_core_loc_conf_t *clcf, ngx_uint_t err); +static ngx_int_t ngx_http_send_refresh(ngx_http_request_t *r); + + +static u_char ngx_http_error_full_tail[] = +"
" NGINX_VER "
" CRLF +"" CRLF +"" CRLF +; + + +static u_char ngx_http_error_tail[] = +"
nginx
" CRLF +"" CRLF +"" CRLF +; + + +static u_char ngx_http_msie_padding[] = +"" CRLF +"" CRLF +"" CRLF +"" CRLF +"" CRLF +"" CRLF +; + + +static u_char ngx_http_msie_refresh_head[] = +"" CRLF; + + +static char ngx_http_error_301_page[] = +"" CRLF +"301 Moved Permanently" CRLF +"" CRLF +"

301 Moved Permanently

" CRLF +; + + +static char ngx_http_error_302_page[] = +"" CRLF +"302 Found" CRLF +"" CRLF +"

302 Found

" CRLF +; + + +static char ngx_http_error_303_page[] = +"" CRLF +"303 See Other" CRLF +"" CRLF +"

303 See Other

" CRLF +; + + +static char ngx_http_error_307_page[] = +"" CRLF +"307 Temporary Redirect" CRLF +"" CRLF +"

307 Temporary Redirect

" CRLF +; + + +static char ngx_http_error_400_page[] = +"" CRLF +"400 Bad Request" CRLF +"" CRLF +"

400 Bad Request

" CRLF +; + + +static char ngx_http_error_401_page[] = +"" CRLF +"401 Authorization Required" CRLF +"" CRLF +"

401 Authorization Required

" CRLF +; + + +static char ngx_http_error_402_page[] = +"" CRLF +"402 Payment Required" CRLF +"" CRLF +"

402 Payment Required

" CRLF +; + + +static char ngx_http_error_403_page[] = +"" CRLF +"403 Forbidden" CRLF +"" CRLF +"

403 Forbidden

" CRLF +; + + +static char ngx_http_error_404_page[] = +"" CRLF +"404 Not Found" CRLF +"" CRLF +"

404 Not Found

" CRLF +; + + +static char ngx_http_error_405_page[] = +"" CRLF +"405 Not Allowed" CRLF +"" CRLF +"

405 Not Allowed

" CRLF +; + + +static char ngx_http_error_406_page[] = +"" CRLF +"406 Not Acceptable" CRLF +"" CRLF +"

406 Not Acceptable

" CRLF +; + + +static char ngx_http_error_408_page[] = +"" CRLF +"408 Request Time-out" CRLF +"" CRLF +"

408 Request Time-out

" CRLF +; + + +static char ngx_http_error_409_page[] = +"" CRLF +"409 Conflict" CRLF +"" CRLF +"

409 Conflict

" CRLF +; + + +static char ngx_http_error_410_page[] = +"" CRLF +"410 Gone" CRLF +"" CRLF +"

410 Gone

" CRLF +; + + +static char ngx_http_error_411_page[] = +"" CRLF +"411 Length Required" CRLF +"" CRLF +"

411 Length Required

" CRLF +; + + +static char ngx_http_error_412_page[] = +"" CRLF +"412 Precondition Failed" CRLF +"" CRLF +"

412 Precondition Failed

" CRLF +; + + +static char ngx_http_error_413_page[] = +"" CRLF +"413 Request Entity Too Large" CRLF +"" CRLF +"

413 Request Entity Too Large

" CRLF +; + + +static char ngx_http_error_414_page[] = +"" CRLF +"414 Request-URI Too Large" CRLF +"" CRLF +"

414 Request-URI Too Large

" CRLF +; + + +static char ngx_http_error_415_page[] = +"" CRLF +"415 Unsupported Media Type" CRLF +"" CRLF +"

415 Unsupported Media Type

" CRLF +; + + +static char ngx_http_error_416_page[] = +"" CRLF +"416 Requested Range Not Satisfiable" CRLF +"" CRLF +"

416 Requested Range Not Satisfiable

" CRLF +; + + +static char ngx_http_error_494_page[] = +"" CRLF +"400 Request Header Or Cookie Too Large" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
Request Header Or Cookie Too Large
" CRLF +; + + +static char ngx_http_error_495_page[] = +"" CRLF +"400 The SSL certificate error" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
The SSL certificate error
" CRLF +; + + +static char ngx_http_error_496_page[] = +"" CRLF +"400 No required SSL certificate was sent" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
No required SSL certificate was sent
" CRLF +; + + +static char ngx_http_error_497_page[] = +"" CRLF +"400 The plain HTTP request was sent to HTTPS port" +CRLF +"" CRLF +"

400 Bad Request

" CRLF +"
The plain HTTP request was sent to HTTPS port
" CRLF +; + + +static char ngx_http_error_500_page[] = +"" CRLF +"500 Internal Server Error" CRLF +"" CRLF +"

500 Internal Server Error

" CRLF +; + + +static char ngx_http_error_501_page[] = +"" CRLF +"501 Not Implemented" CRLF +"" CRLF +"

501 Not Implemented

" CRLF +; + + +static char ngx_http_error_502_page[] = +"" CRLF +"502 Bad Gateway" CRLF +"" CRLF +"

502 Bad Gateway

" CRLF +; + + +static char ngx_http_error_503_page[] = +"" CRLF +"503 Service Temporarily Unavailable" CRLF +"" CRLF +"

503 Service Temporarily Unavailable

" CRLF +; + + +static char ngx_http_error_504_page[] = +"" CRLF +"504 Gateway Time-out" CRLF +"" CRLF +"

504 Gateway Time-out

" CRLF +; + + +static char ngx_http_error_507_page[] = +"" CRLF +"507 Insufficient Storage" CRLF +"" CRLF +"

507 Insufficient Storage

" CRLF +; + + +static ngx_str_t ngx_http_error_pages[] = { + + ngx_null_string, /* 201, 204 */ + +#define NGX_HTTP_LAST_2XX 202 +#define NGX_HTTP_OFF_3XX (NGX_HTTP_LAST_2XX - 201) + + /* ngx_null_string, */ /* 300 */ + ngx_string(ngx_http_error_301_page), + ngx_string(ngx_http_error_302_page), + ngx_string(ngx_http_error_303_page), + ngx_null_string, /* 304 */ + ngx_null_string, /* 305 */ + ngx_null_string, /* 306 */ + ngx_string(ngx_http_error_307_page), + +#define NGX_HTTP_LAST_3XX 308 +#define NGX_HTTP_OFF_4XX (NGX_HTTP_LAST_3XX - 301 + NGX_HTTP_OFF_3XX) + + ngx_string(ngx_http_error_400_page), + ngx_string(ngx_http_error_401_page), + ngx_string(ngx_http_error_402_page), + ngx_string(ngx_http_error_403_page), + ngx_string(ngx_http_error_404_page), + ngx_string(ngx_http_error_405_page), + ngx_string(ngx_http_error_406_page), + ngx_null_string, /* 407 */ + ngx_string(ngx_http_error_408_page), + ngx_string(ngx_http_error_409_page), + ngx_string(ngx_http_error_410_page), + ngx_string(ngx_http_error_411_page), + ngx_string(ngx_http_error_412_page), + ngx_string(ngx_http_error_413_page), + ngx_string(ngx_http_error_414_page), + ngx_string(ngx_http_error_415_page), + ngx_string(ngx_http_error_416_page), + +#define NGX_HTTP_LAST_4XX 417 +#define NGX_HTTP_OFF_5XX (NGX_HTTP_LAST_4XX - 400 + NGX_HTTP_OFF_4XX) + + ngx_string(ngx_http_error_494_page), /* 494, request header too large */ + ngx_string(ngx_http_error_495_page), /* 495, https certificate error */ + ngx_string(ngx_http_error_496_page), /* 496, https no certificate */ + ngx_string(ngx_http_error_497_page), /* 497, http to https */ + ngx_string(ngx_http_error_404_page), /* 498, canceled */ + ngx_null_string, /* 499, client has closed connection */ + + ngx_string(ngx_http_error_500_page), + ngx_string(ngx_http_error_501_page), + ngx_string(ngx_http_error_502_page), + ngx_string(ngx_http_error_503_page), + ngx_string(ngx_http_error_504_page), + ngx_null_string, /* 505 */ + ngx_null_string, /* 506 */ + ngx_string(ngx_http_error_507_page) + +#define NGX_HTTP_LAST_5XX 508 + +}; + + +ngx_int_t +ngx_http_special_response_handler(ngx_http_request_t *r, ngx_int_t error) +{ + ngx_uint_t i, err; + ngx_http_err_page_t *err_page; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http special response: %i, \"%V?%V\"", + error, &r->uri, &r->args); + + r->err_status = error; + + if (r->keepalive) { + switch (error) { + case NGX_HTTP_BAD_REQUEST: + case NGX_HTTP_REQUEST_ENTITY_TOO_LARGE: + case NGX_HTTP_REQUEST_URI_TOO_LARGE: + case NGX_HTTP_TO_HTTPS: + case NGX_HTTPS_CERT_ERROR: + case NGX_HTTPS_NO_CERT: + case NGX_HTTP_INTERNAL_SERVER_ERROR: + case NGX_HTTP_NOT_IMPLEMENTED: + r->keepalive = 0; + } + } + + if (r->lingering_close) { + switch (error) { + case NGX_HTTP_BAD_REQUEST: + case NGX_HTTP_TO_HTTPS: + case NGX_HTTPS_CERT_ERROR: + case NGX_HTTPS_NO_CERT: + r->lingering_close = 0; + } + } + + r->headers_out.content_type.len = 0; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (!r->error_page && clcf->error_pages && r->uri_changes != 0) { + + if (clcf->recursive_error_pages == 0) { + r->error_page = 1; + } + + err_page = clcf->error_pages->elts; + + for (i = 0; i < clcf->error_pages->nelts; i++) { + if (err_page[i].status == error) { + return ngx_http_send_error_page(r, &err_page[i]); + } + } + } + + r->expect_tested = 1; + + if (ngx_http_discard_request_body(r) != NGX_OK) { + r->keepalive = 0; + } + + if (clcf->msie_refresh + && r->headers_in.msie + && (error == NGX_HTTP_MOVED_PERMANENTLY + || error == NGX_HTTP_MOVED_TEMPORARILY)) + { + return ngx_http_send_refresh(r); + } + + if (error == NGX_HTTP_CREATED) { + /* 201 */ + err = 0; + + } else if (error == NGX_HTTP_NO_CONTENT) { + /* 204 */ + err = 0; + + } else if (error >= NGX_HTTP_MOVED_PERMANENTLY + && error < NGX_HTTP_LAST_3XX) + { + /* 3XX */ + err = error - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX; + + } else if (error >= NGX_HTTP_BAD_REQUEST + && error < NGX_HTTP_LAST_4XX) + { + /* 4XX */ + err = error - NGX_HTTP_BAD_REQUEST + NGX_HTTP_OFF_4XX; + + } else if (error >= NGX_HTTP_NGINX_CODES + && error < NGX_HTTP_LAST_5XX) + { + /* 49X, 5XX */ + err = error - NGX_HTTP_NGINX_CODES + NGX_HTTP_OFF_5XX; + switch (error) { + case NGX_HTTP_TO_HTTPS: + case NGX_HTTPS_CERT_ERROR: + case NGX_HTTPS_NO_CERT: + case NGX_HTTP_REQUEST_HEADER_TOO_LARGE: + r->err_status = NGX_HTTP_BAD_REQUEST; + break; + } + + } else { + /* unknown code, zero body */ + err = 0; + } + + return ngx_http_send_special_response(r, clcf, err); +} + + +ngx_int_t +ngx_http_filter_finalize_request(ngx_http_request_t *r, ngx_module_t *m, + ngx_int_t error) +{ + void *ctx; + ngx_int_t rc; + + ngx_http_clean_header(r); + + ctx = NULL; + + if (m) { + ctx = r->ctx[m->ctx_index]; + } + + /* clear the modules contexts */ + ngx_memzero(r->ctx, sizeof(void *) * ngx_http_max_module); + + if (m) { + r->ctx[m->ctx_index] = ctx; + } + + r->filter_finalize = 1; + + rc = ngx_http_special_response_handler(r, error); + + /* NGX_ERROR resets any pending data */ + + switch (rc) { + + case NGX_OK: + case NGX_DONE: + return NGX_ERROR; + + default: + return rc; + } +} + + +void +ngx_http_clean_header(ngx_http_request_t *r) +{ + ngx_memzero(&r->headers_out.status, + sizeof(ngx_http_headers_out_t) + - offsetof(ngx_http_headers_out_t, status)); + + r->headers_out.headers.part.nelts = 0; + r->headers_out.headers.part.next = NULL; + r->headers_out.headers.last = &r->headers_out.headers.part; + + r->headers_out.content_length_n = -1; + r->headers_out.last_modified_time = -1; +} + + +static ngx_int_t +ngx_http_send_error_page(ngx_http_request_t *r, ngx_http_err_page_t *err_page) +{ + ngx_int_t overwrite; + ngx_str_t uri, args; + ngx_table_elt_t *location; + ngx_http_core_loc_conf_t *clcf; + + overwrite = err_page->overwrite; + + if (overwrite && overwrite != NGX_HTTP_OK) { + r->expect_tested = 1; + } + + if (overwrite >= 0) { + r->err_status = overwrite; + } + + if (ngx_http_complex_value(r, &err_page->value, &uri) != NGX_OK) { + return NGX_ERROR; + } + + if (uri.len && uri.data[0] == '/') { + + if (err_page->value.lengths) { + ngx_http_split_args(r, &uri, &args); + + } else { + args = err_page->args; + } + + if (r->method != NGX_HTTP_HEAD) { + r->method = NGX_HTTP_GET; + r->method_name = ngx_http_core_get_method; + } + + return ngx_http_internal_redirect(r, &uri, &args); + } + + if (uri.len && uri.data[0] == '@') { + return ngx_http_named_location(r, &uri); + } + + location = ngx_list_push(&r->headers_out.headers); + + if (location == NULL) { + return NGX_ERROR; + } + + if (overwrite != NGX_HTTP_MOVED_PERMANENTLY + && overwrite != NGX_HTTP_MOVED_TEMPORARILY + && overwrite != NGX_HTTP_SEE_OTHER + && overwrite != NGX_HTTP_TEMPORARY_REDIRECT) + { + r->err_status = NGX_HTTP_MOVED_TEMPORARILY; + } + + location->hash = 1; + ngx_str_set(&location->key, "Location"); + location->value = uri; + + ngx_http_clear_location(r); + + r->headers_out.location = location; + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (clcf->msie_refresh && r->headers_in.msie) { + return ngx_http_send_refresh(r); + } + + return ngx_http_send_special_response(r, clcf, r->err_status + - NGX_HTTP_MOVED_PERMANENTLY + + NGX_HTTP_OFF_3XX); +} + + +static ngx_int_t +ngx_http_send_special_response(ngx_http_request_t *r, + ngx_http_core_loc_conf_t *clcf, ngx_uint_t err) +{ + u_char *tail; + size_t len; + ngx_int_t rc; + ngx_buf_t *b; + ngx_uint_t msie_padding; + ngx_chain_t out[3]; + + if (clcf->server_tokens) { + len = sizeof(ngx_http_error_full_tail) - 1; + tail = ngx_http_error_full_tail; + + } else { + len = sizeof(ngx_http_error_tail) - 1; + tail = ngx_http_error_tail; + } + + msie_padding = 0; + + if (ngx_http_error_pages[err].len) { + r->headers_out.content_length_n = ngx_http_error_pages[err].len + len; + if (clcf->msie_padding + && (r->headers_in.msie || r->headers_in.chrome) + && r->http_version >= NGX_HTTP_VERSION_10 + && err >= NGX_HTTP_OFF_4XX) + { + r->headers_out.content_length_n += + sizeof(ngx_http_msie_padding) - 1; + msie_padding = 1; + } + + r->headers_out.content_type_len = sizeof("text/html") - 1; + ngx_str_set(&r->headers_out.content_type, "text/html"); + r->headers_out.content_type_lowcase = NULL; + + } else { + r->headers_out.content_length_n = 0; + } + + if (r->headers_out.content_length) { + r->headers_out.content_length->hash = 0; + r->headers_out.content_length = NULL; + } + + ngx_http_clear_accept_ranges(r); + ngx_http_clear_last_modified(r); + ngx_http_clear_etag(r); + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || r->header_only) { + return rc; + } + + if (ngx_http_error_pages[err].len == 0) { + return ngx_http_send_special(r, NGX_HTTP_LAST); + } + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + b->memory = 1; + b->pos = ngx_http_error_pages[err].data; + b->last = ngx_http_error_pages[err].data + ngx_http_error_pages[err].len; + + out[0].buf = b; + out[0].next = &out[1]; + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + b->memory = 1; + + b->pos = tail; + b->last = tail + len; + + out[1].buf = b; + out[1].next = NULL; + + if (msie_padding) { + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NGX_ERROR; + } + + b->memory = 1; + b->pos = ngx_http_msie_padding; + b->last = ngx_http_msie_padding + sizeof(ngx_http_msie_padding) - 1; + + out[1].next = &out[2]; + out[2].buf = b; + out[2].next = NULL; + } + + if (r == r->main) { + b->last_buf = 1; + } + + b->last_in_chain = 1; + + return ngx_http_output_filter(r, &out[0]); +} + + +static ngx_int_t +ngx_http_send_refresh(ngx_http_request_t *r) +{ + u_char *p, *location; + size_t len, size; + uintptr_t escape; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t out; + + len = r->headers_out.location->value.len; + location = r->headers_out.location->value.data; + + escape = 2 * ngx_escape_uri(NULL, location, len, NGX_ESCAPE_REFRESH); + + size = sizeof(ngx_http_msie_refresh_head) - 1 + + escape + len + + sizeof(ngx_http_msie_refresh_tail) - 1; + + r->err_status = NGX_HTTP_OK; + + r->headers_out.content_type_len = sizeof("text/html") - 1; + ngx_str_set(&r->headers_out.content_type, "text/html"); + r->headers_out.content_type_lowcase = NULL; + + r->headers_out.location->hash = 0; + r->headers_out.location = NULL; + + r->headers_out.content_length_n = size; + + if (r->headers_out.content_length) { + r->headers_out.content_length->hash = 0; + r->headers_out.content_length = NULL; + } + + ngx_http_clear_accept_ranges(r); + ngx_http_clear_last_modified(r); + ngx_http_clear_etag(r); + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || r->header_only) { + return rc; + } + + b = ngx_create_temp_buf(r->pool, size); + if (b == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(b->pos, ngx_http_msie_refresh_head, + sizeof(ngx_http_msie_refresh_head) - 1); + + if (escape == 0) { + p = ngx_cpymem(p, location, len); + + } else { + p = (u_char *) ngx_escape_uri(p, location, len, NGX_ESCAPE_REFRESH); + } + + b->last = ngx_cpymem(p, ngx_http_msie_refresh_tail, + sizeof(ngx_http_msie_refresh_tail) - 1); + + b->last_buf = 1; + b->last_in_chain = 1; + + out.buf = b; + out.next = NULL; + + return ngx_http_output_filter(r, &out); +} From 5982f05fe28e7eff7ee38d10b0bac853c7ec0587 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 15:16:10 +0000 Subject: [PATCH 3/4] commit patch 17491718 --- src/core/ngx_resolver.c | 8 +- src/core/ngx_resolver.c.orig | 3178 ++++++++++++++++++++++++++++++++++ 2 files changed, 3182 insertions(+), 4 deletions(-) create mode 100644 src/core/ngx_resolver.c.orig diff --git a/src/core/ngx_resolver.c b/src/core/ngx_resolver.c index 7b60abd49d..cdb4fed169 100644 --- a/src/core/ngx_resolver.c +++ b/src/core/ngx_resolver.c @@ -2835,15 +2835,15 @@ ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src, n = *src++; } else { + if (dst != name->data) { + *dst++ = '.'; + } + ngx_strlow(dst, src, n); dst += n; src += n; n = *src++; - - if (n != 0) { - *dst++ = '.'; - } } if (n == 0) { diff --git a/src/core/ngx_resolver.c.orig b/src/core/ngx_resolver.c.orig new file mode 100644 index 0000000000..7b60abd49d --- /dev/null +++ b/src/core/ngx_resolver.c.orig @@ -0,0 +1,3178 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_RESOLVER_UDP_SIZE 4096 + + +typedef struct { + u_char ident_hi; + u_char ident_lo; + u_char flags_hi; + u_char flags_lo; + u_char nqs_hi; + u_char nqs_lo; + u_char nan_hi; + u_char nan_lo; + u_char nns_hi; + u_char nns_lo; + u_char nar_hi; + u_char nar_lo; +} ngx_resolver_hdr_t; + + +typedef struct { + u_char type_hi; + u_char type_lo; + u_char class_hi; + u_char class_lo; +} ngx_resolver_qs_t; + + +typedef struct { + u_char type_hi; + u_char type_lo; + u_char class_hi; + u_char class_lo; + u_char ttl[4]; + u_char len_hi; + u_char len_lo; +} ngx_resolver_an_t; + + +#define ngx_resolver_node(n) \ + (ngx_resolver_node_t *) \ + ((u_char *) (n) - offsetof(ngx_resolver_node_t, node)) + + +ngx_int_t ngx_udp_connect(ngx_udp_connection_t *uc); + + +static void ngx_resolver_cleanup(void *data); +static void ngx_resolver_cleanup_tree(ngx_resolver_t *r, ngx_rbtree_t *tree); +static ngx_int_t ngx_resolve_name_locked(ngx_resolver_t *r, + ngx_resolver_ctx_t *ctx, ngx_str_t *name); +static void ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree, + ngx_queue_t *queue); +static ngx_int_t ngx_resolver_send_query(ngx_resolver_t *r, + ngx_resolver_node_t *rn); +static ngx_int_t ngx_resolver_create_name_query(ngx_resolver_t *r, + ngx_resolver_node_t *rn, ngx_str_t *name); +static ngx_int_t ngx_resolver_create_addr_query(ngx_resolver_t *r, + ngx_resolver_node_t *rn, ngx_addr_t *addr); +static void ngx_resolver_resend_handler(ngx_event_t *ev); +static time_t ngx_resolver_resend(ngx_resolver_t *r, ngx_rbtree_t *tree, + ngx_queue_t *queue); +static ngx_uint_t ngx_resolver_resend_empty(ngx_resolver_t *r); +static void ngx_resolver_read_response(ngx_event_t *rev); +static void ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, + size_t n); +static void ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t n, + ngx_uint_t ident, ngx_uint_t code, ngx_uint_t qtype, + ngx_uint_t nan, ngx_uint_t ans); +static void ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n, + ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan); +static ngx_resolver_node_t *ngx_resolver_lookup_name(ngx_resolver_t *r, + ngx_str_t *name, uint32_t hash); +static ngx_resolver_node_t *ngx_resolver_lookup_addr(ngx_resolver_t *r, + in_addr_t addr); +static void ngx_resolver_rbtree_insert_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +static ngx_int_t ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, + u_char *buf, u_char *src, u_char *last); +static void ngx_resolver_timeout_handler(ngx_event_t *ev); +static void ngx_resolver_free_node(ngx_resolver_t *r, ngx_resolver_node_t *rn); +static void *ngx_resolver_alloc(ngx_resolver_t *r, size_t size); +static void *ngx_resolver_calloc(ngx_resolver_t *r, size_t size); +static void ngx_resolver_free(ngx_resolver_t *r, void *p); +static void ngx_resolver_free_locked(ngx_resolver_t *r, void *p); +static void *ngx_resolver_dup(ngx_resolver_t *r, void *src, size_t size); +static ngx_addr_t *ngx_resolver_export(ngx_resolver_t *r, + ngx_resolver_node_t *rn, ngx_uint_t rotate); +static u_char *ngx_resolver_log_error(ngx_log_t *log, u_char *buf, size_t len); + +#if (NGX_HAVE_INET6) +static void ngx_resolver_rbtree_insert_addr6_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +static ngx_resolver_node_t *ngx_resolver_lookup_addr6(ngx_resolver_t *r, + struct in6_addr *addr, uint32_t hash); +#endif + + +ngx_resolver_t * +ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n) +{ + ngx_str_t s; + ngx_url_t u; + ngx_uint_t i, j; + ngx_resolver_t *r; + ngx_pool_cleanup_t *cln; + ngx_udp_connection_t *uc; + + cln = ngx_pool_cleanup_add(cf->pool, 0); + if (cln == NULL) { + return NULL; + } + + cln->handler = ngx_resolver_cleanup; + + r = ngx_calloc(sizeof(ngx_resolver_t), cf->log); + if (r == NULL) { + return NULL; + } + + cln->data = r; + + r->event = ngx_calloc(sizeof(ngx_event_t), cf->log); + if (r->event == NULL) { + return NULL; + } + + ngx_rbtree_init(&r->name_rbtree, &r->name_sentinel, + ngx_resolver_rbtree_insert_value); + + ngx_rbtree_init(&r->addr_rbtree, &r->addr_sentinel, + ngx_rbtree_insert_value); + + ngx_queue_init(&r->name_resend_queue); + ngx_queue_init(&r->addr_resend_queue); + + ngx_queue_init(&r->name_expire_queue); + ngx_queue_init(&r->addr_expire_queue); + +#if (NGX_HAVE_INET6) + r->ipv6 = 1; + + ngx_rbtree_init(&r->addr6_rbtree, &r->addr6_sentinel, + ngx_resolver_rbtree_insert_addr6_value); + + ngx_queue_init(&r->addr6_resend_queue); + + ngx_queue_init(&r->addr6_expire_queue); +#endif + + r->event->handler = ngx_resolver_resend_handler; + r->event->data = r; + r->event->log = &cf->cycle->new_log; + r->ident = -1; + + r->resend_timeout = 5; + r->expire = 30; + r->valid = 0; + + r->log = &cf->cycle->new_log; + r->log_level = NGX_LOG_ERR; + + if (n) { + if (ngx_array_init(&r->udp_connections, cf->pool, n, + sizeof(ngx_udp_connection_t)) + != NGX_OK) + { + return NULL; + } + } + + for (i = 0; i < n; i++) { + if (ngx_strncmp(names[i].data, "valid=", 6) == 0) { + s.len = names[i].len - 6; + s.data = names[i].data + 6; + + r->valid = ngx_parse_time(&s, 1); + + if (r->valid == (time_t) NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter: %V", &names[i]); + return NULL; + } + + continue; + } + +#if (NGX_HAVE_INET6) + if (ngx_strncmp(names[i].data, "ipv6=", 5) == 0) { + + if (ngx_strcmp(&names[i].data[5], "on") == 0) { + r->ipv6 = 1; + + } else if (ngx_strcmp(&names[i].data[5], "off") == 0) { + r->ipv6 = 0; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter: %V", &names[i]); + return NULL; + } + + continue; + } +#endif + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = names[i]; + u.default_port = 53; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in resolver \"%V\"", + u.err, &u.url); + } + + return NULL; + } + + uc = ngx_array_push_n(&r->udp_connections, u.naddrs); + if (uc == NULL) { + return NULL; + } + + ngx_memzero(uc, u.naddrs * sizeof(ngx_udp_connection_t)); + + for (j = 0; j < u.naddrs; j++) { + uc[j].sockaddr = u.addrs[j].sockaddr; + uc[j].socklen = u.addrs[j].socklen; + uc[j].server = u.addrs[j].name; + } + } + + return r; +} + + +static void +ngx_resolver_cleanup(void *data) +{ + ngx_resolver_t *r = data; + + ngx_uint_t i; + ngx_udp_connection_t *uc; + + if (r) { + ngx_log_debug0(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "cleanup resolver"); + + ngx_resolver_cleanup_tree(r, &r->name_rbtree); + + ngx_resolver_cleanup_tree(r, &r->addr_rbtree); + +#if (NGX_HAVE_INET6) + ngx_resolver_cleanup_tree(r, &r->addr6_rbtree); +#endif + + if (r->event) { + ngx_free(r->event); + } + + + uc = r->udp_connections.elts; + + for (i = 0; i < r->udp_connections.nelts; i++) { + if (uc[i].connection) { + ngx_close_connection(uc[i].connection); + } + } + + ngx_free(r); + } +} + + +static void +ngx_resolver_cleanup_tree(ngx_resolver_t *r, ngx_rbtree_t *tree) +{ + ngx_resolver_ctx_t *ctx, *next; + ngx_resolver_node_t *rn; + + while (tree->root != tree->sentinel) { + + rn = ngx_resolver_node(ngx_rbtree_min(tree->root, tree->sentinel)); + + ngx_queue_remove(&rn->queue); + + for (ctx = rn->waiting; ctx; ctx = next) { + next = ctx->next; + + if (ctx->event) { + ngx_resolver_free(r, ctx->event); + } + + ngx_resolver_free(r, ctx); + } + + ngx_rbtree_delete(tree, &rn->node); + + ngx_resolver_free_node(r, rn); + } +} + + +ngx_resolver_ctx_t * +ngx_resolve_start(ngx_resolver_t *r, ngx_resolver_ctx_t *temp) +{ + in_addr_t addr; + ngx_resolver_ctx_t *ctx; + + if (temp) { + addr = ngx_inet_addr(temp->name.data, temp->name.len); + + if (addr != INADDR_NONE) { + temp->resolver = r; + temp->state = NGX_OK; + temp->naddrs = 1; + temp->addrs = &temp->addr; + temp->addr.sockaddr = (struct sockaddr *) &temp->sin; + temp->addr.socklen = sizeof(struct sockaddr_in); + ngx_memzero(&temp->sin, sizeof(struct sockaddr_in)); + temp->sin.sin_family = AF_INET; + temp->sin.sin_addr.s_addr = addr; + temp->quick = 1; + + return temp; + } + } + + if (r->udp_connections.nelts == 0) { + return NGX_NO_RESOLVER; + } + + ctx = ngx_resolver_calloc(r, sizeof(ngx_resolver_ctx_t)); + + if (ctx) { + ctx->resolver = r; + } + + return ctx; +} + + +ngx_int_t +ngx_resolve_name(ngx_resolver_ctx_t *ctx) +{ + ngx_int_t rc; + ngx_resolver_t *r; + + r = ctx->resolver; + + if (ctx->name.len > 0 && ctx->name.data[ctx->name.len - 1] == '.') { + ctx->name.len--; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolve: \"%V\"", &ctx->name); + + if (ctx->quick) { + ctx->handler(ctx); + return NGX_OK; + } + + /* lock name mutex */ + + rc = ngx_resolve_name_locked(r, ctx, &ctx->name); + + if (rc == NGX_OK) { + return NGX_OK; + } + + /* unlock name mutex */ + + if (rc == NGX_AGAIN) { + return NGX_OK; + } + + /* NGX_ERROR */ + + if (ctx->event) { + ngx_resolver_free(r, ctx->event); + } + + ngx_resolver_free(r, ctx); + + return NGX_ERROR; +} + + +void +ngx_resolve_name_done(ngx_resolver_ctx_t *ctx) +{ + ngx_resolver_t *r; + ngx_resolver_ctx_t *w, **p; + ngx_resolver_node_t *rn; + + r = ctx->resolver; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolve name done: %i", ctx->state); + + if (ctx->quick) { + return; + } + + if (ctx->event && ctx->event->timer_set) { + ngx_del_timer(ctx->event); + } + + /* lock name mutex */ + + if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) { + + rn = ctx->node; + + if (rn) { + p = &rn->waiting; + w = rn->waiting; + + while (w) { + if (w == ctx) { + *p = w->next; + + goto done; + } + + p = &w->next; + w = w->next; + } + } + + ngx_log_error(NGX_LOG_ALERT, r->log, 0, + "could not cancel %V resolving", &ctx->name); + } + +done: + + ngx_resolver_expire(r, &r->name_rbtree, &r->name_expire_queue); + + /* unlock name mutex */ + + /* lock alloc mutex */ + + if (ctx->event) { + ngx_resolver_free_locked(r, ctx->event); + } + + ngx_resolver_free_locked(r, ctx); + + /* unlock alloc mutex */ + + if (r->event->timer_set && ngx_resolver_resend_empty(r)) { + ngx_del_timer(r->event); + } +} + + +static ngx_int_t +ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx, + ngx_str_t *name) +{ + uint32_t hash; + ngx_int_t rc; + ngx_str_t cname; + ngx_uint_t naddrs; + ngx_addr_t *addrs; + ngx_resolver_ctx_t *next, *last; + ngx_resolver_node_t *rn; + + ngx_strlow(name->data, name->data, name->len); + + hash = ngx_crc32_short(name->data, name->len); + + rn = ngx_resolver_lookup_name(r, name, hash); + + if (rn) { + + /* ctx can be a list after NGX_RESOLVE_CNAME */ + for (last = ctx; last->next; last = last->next); + + if (rn->valid >= ngx_time()) { + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, r->log, 0, "resolve cached"); + + ngx_queue_remove(&rn->queue); + + rn->expire = ngx_time() + r->expire; + + ngx_queue_insert_head(&r->name_expire_queue, &rn->queue); + + naddrs = (rn->naddrs == (u_short) -1) ? 0 : rn->naddrs; +#if (NGX_HAVE_INET6) + naddrs += (rn->naddrs6 == (u_short) -1) ? 0 : rn->naddrs6; +#endif + + if (naddrs) { + + if (naddrs == 1 && rn->naddrs == 1) { + addrs = NULL; + + } else { + addrs = ngx_resolver_export(r, rn, 1); + if (addrs == NULL) { + return NGX_ERROR; + } + } + + last->next = rn->waiting; + rn->waiting = NULL; + + /* unlock name mutex */ + + do { + ctx->state = NGX_OK; + ctx->naddrs = naddrs; + + if (addrs == NULL) { + ctx->addrs = &ctx->addr; + ctx->addr.sockaddr = (struct sockaddr *) &ctx->sin; + ctx->addr.socklen = sizeof(struct sockaddr_in); + ngx_memzero(&ctx->sin, sizeof(struct sockaddr_in)); + ctx->sin.sin_family = AF_INET; + ctx->sin.sin_addr.s_addr = rn->u.addr; + + } else { + ctx->addrs = addrs; + } + + next = ctx->next; + + ctx->handler(ctx); + + ctx = next; + } while (ctx); + + if (addrs != NULL) { + ngx_resolver_free(r, addrs->sockaddr); + ngx_resolver_free(r, addrs); + } + + return NGX_OK; + } + + /* NGX_RESOLVE_CNAME */ + + if (ctx->recursion++ < NGX_RESOLVER_MAX_RECURSION) { + + cname.len = rn->cnlen; + cname.data = rn->u.cname; + + return ngx_resolve_name_locked(r, ctx, &cname); + } + + last->next = rn->waiting; + rn->waiting = NULL; + + /* unlock name mutex */ + + do { + ctx->state = NGX_RESOLVE_NXDOMAIN; + next = ctx->next; + + ctx->handler(ctx); + + ctx = next; + } while (ctx); + + return NGX_OK; + } + + if (rn->waiting) { + + if (ctx->event == NULL) { + ctx->event = ngx_resolver_calloc(r, sizeof(ngx_event_t)); + if (ctx->event == NULL) { + return NGX_ERROR; + } + + ctx->event->handler = ngx_resolver_timeout_handler; + ctx->event->data = ctx; + ctx->event->log = r->log; + ctx->ident = -1; + + ngx_add_timer(ctx->event, ctx->timeout); + } + + last->next = rn->waiting; + rn->waiting = ctx; + ctx->state = NGX_AGAIN; + + do { + ctx->node = rn; + ctx = ctx->next; + } while (ctx); + + return NGX_AGAIN; + } + + ngx_queue_remove(&rn->queue); + + /* lock alloc mutex */ + + if (rn->query) { + ngx_resolver_free_locked(r, rn->query); + rn->query = NULL; +#if (NGX_HAVE_INET6) + rn->query6 = NULL; +#endif + } + + if (rn->cnlen) { + ngx_resolver_free_locked(r, rn->u.cname); + } + + if (rn->naddrs > 1 && rn->naddrs != (u_short) -1) { + ngx_resolver_free_locked(r, rn->u.addrs); + } + +#if (NGX_HAVE_INET6) + if (rn->naddrs6 > 1 && rn->naddrs6 != (u_short) -1) { + ngx_resolver_free_locked(r, rn->u6.addrs6); + } +#endif + + /* unlock alloc mutex */ + + } else { + + rn = ngx_resolver_alloc(r, sizeof(ngx_resolver_node_t)); + if (rn == NULL) { + return NGX_ERROR; + } + + rn->name = ngx_resolver_dup(r, name->data, name->len); + if (rn->name == NULL) { + ngx_resolver_free(r, rn); + return NGX_ERROR; + } + + rn->node.key = hash; + rn->nlen = (u_short) name->len; + rn->query = NULL; +#if (NGX_HAVE_INET6) + rn->query6 = NULL; +#endif + + ngx_rbtree_insert(&r->name_rbtree, &rn->node); + } + + rc = ngx_resolver_create_name_query(r, rn, name); + + if (rc == NGX_ERROR) { + goto failed; + } + + if (rc == NGX_DECLINED) { + ngx_rbtree_delete(&r->name_rbtree, &rn->node); + + ngx_resolver_free(r, rn->query); + ngx_resolver_free(r, rn->name); + ngx_resolver_free(r, rn); + + do { + ctx->state = NGX_RESOLVE_NXDOMAIN; + next = ctx->next; + + ctx->handler(ctx); + + ctx = next; + } while (ctx); + + return NGX_OK; + } + + rn->naddrs = (u_short) -1; +#if (NGX_HAVE_INET6) + rn->naddrs6 = r->ipv6 ? (u_short) -1 : 0; +#endif + + if (ngx_resolver_send_query(r, rn) != NGX_OK) { + goto failed; + } + + if (ctx->event == NULL) { + ctx->event = ngx_resolver_calloc(r, sizeof(ngx_event_t)); + if (ctx->event == NULL) { + goto failed; + } + + ctx->event->handler = ngx_resolver_timeout_handler; + ctx->event->data = ctx; + ctx->event->log = r->log; + ctx->ident = -1; + + ngx_add_timer(ctx->event, ctx->timeout); + } + + if (ngx_queue_empty(&r->name_resend_queue)) { + ngx_add_timer(r->event, (ngx_msec_t) (r->resend_timeout * 1000)); + } + + rn->expire = ngx_time() + r->resend_timeout; + + ngx_queue_insert_head(&r->name_resend_queue, &rn->queue); + + rn->code = 0; + rn->cnlen = 0; + rn->valid = 0; + rn->ttl = NGX_MAX_UINT32_VALUE; + rn->waiting = ctx; + + ctx->state = NGX_AGAIN; + + do { + ctx->node = rn; + ctx = ctx->next; + } while (ctx); + + return NGX_AGAIN; + +failed: + + ngx_rbtree_delete(&r->name_rbtree, &rn->node); + + if (rn->query) { + ngx_resolver_free(r, rn->query); + } + + ngx_resolver_free(r, rn->name); + + ngx_resolver_free(r, rn); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_resolve_addr(ngx_resolver_ctx_t *ctx) +{ + u_char *name; + in_addr_t addr; + ngx_queue_t *resend_queue, *expire_queue; + ngx_rbtree_t *tree; + ngx_resolver_t *r; + struct sockaddr_in *sin; + ngx_resolver_node_t *rn; +#if (NGX_HAVE_INET6) + uint32_t hash; + struct sockaddr_in6 *sin6; +#endif + +#if (NGX_SUPPRESS_WARN) + addr = 0; +#if (NGX_HAVE_INET6) + hash = 0; + sin6 = NULL; +#endif +#endif + + r = ctx->resolver; + + switch (ctx->addr.sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) ctx->addr.sockaddr; + hash = ngx_crc32_short(sin6->sin6_addr.s6_addr, 16); + + /* lock addr mutex */ + + rn = ngx_resolver_lookup_addr6(r, &sin6->sin6_addr, hash); + + tree = &r->addr6_rbtree; + resend_queue = &r->addr6_resend_queue; + expire_queue = &r->addr6_expire_queue; + + break; +#endif + + default: /* AF_INET */ + sin = (struct sockaddr_in *) ctx->addr.sockaddr; + addr = ntohl(sin->sin_addr.s_addr); + + /* lock addr mutex */ + + rn = ngx_resolver_lookup_addr(r, addr); + + tree = &r->addr_rbtree; + resend_queue = &r->addr_resend_queue; + expire_queue = &r->addr_expire_queue; + } + + if (rn) { + + if (rn->valid >= ngx_time()) { + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, r->log, 0, "resolve cached"); + + ngx_queue_remove(&rn->queue); + + rn->expire = ngx_time() + r->expire; + + ngx_queue_insert_head(expire_queue, &rn->queue); + + name = ngx_resolver_dup(r, rn->name, rn->nlen); + if (name == NULL) { + goto failed; + } + + ctx->name.len = rn->nlen; + ctx->name.data = name; + + /* unlock addr mutex */ + + ctx->state = NGX_OK; + + ctx->handler(ctx); + + ngx_resolver_free(r, name); + + return NGX_OK; + } + + if (rn->waiting) { + + ctx->event = ngx_resolver_calloc(r, sizeof(ngx_event_t)); + if (ctx->event == NULL) { + return NGX_ERROR; + } + + ctx->event->handler = ngx_resolver_timeout_handler; + ctx->event->data = ctx; + ctx->event->log = r->log; + ctx->ident = -1; + + ngx_add_timer(ctx->event, ctx->timeout); + + ctx->next = rn->waiting; + rn->waiting = ctx; + ctx->state = NGX_AGAIN; + ctx->node = rn; + + /* unlock addr mutex */ + + return NGX_OK; + } + + ngx_queue_remove(&rn->queue); + + ngx_resolver_free(r, rn->query); + rn->query = NULL; +#if (NGX_HAVE_INET6) + rn->query6 = NULL; +#endif + + } else { + rn = ngx_resolver_alloc(r, sizeof(ngx_resolver_node_t)); + if (rn == NULL) { + goto failed; + } + + switch (ctx->addr.sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + rn->addr6 = sin6->sin6_addr; + rn->node.key = hash; + break; +#endif + + default: /* AF_INET */ + rn->node.key = addr; + } + + rn->query = NULL; +#if (NGX_HAVE_INET6) + rn->query6 = NULL; +#endif + + ngx_rbtree_insert(tree, &rn->node); + } + + if (ngx_resolver_create_addr_query(r, rn, &ctx->addr) != NGX_OK) { + goto failed; + } + + rn->naddrs = (u_short) -1; +#if (NGX_HAVE_INET6) + rn->naddrs6 = (u_short) -1; +#endif + + if (ngx_resolver_send_query(r, rn) != NGX_OK) { + goto failed; + } + + ctx->event = ngx_resolver_calloc(r, sizeof(ngx_event_t)); + if (ctx->event == NULL) { + goto failed; + } + + ctx->event->handler = ngx_resolver_timeout_handler; + ctx->event->data = ctx; + ctx->event->log = r->log; + ctx->ident = -1; + + ngx_add_timer(ctx->event, ctx->timeout); + + if (ngx_queue_empty(resend_queue)) { + ngx_add_timer(r->event, (ngx_msec_t) (r->resend_timeout * 1000)); + } + + rn->expire = ngx_time() + r->resend_timeout; + + ngx_queue_insert_head(resend_queue, &rn->queue); + + rn->code = 0; + rn->cnlen = 0; + rn->name = NULL; + rn->nlen = 0; + rn->valid = 0; + rn->ttl = NGX_MAX_UINT32_VALUE; + rn->waiting = ctx; + + /* unlock addr mutex */ + + ctx->state = NGX_AGAIN; + ctx->node = rn; + + return NGX_OK; + +failed: + + if (rn) { + ngx_rbtree_delete(tree, &rn->node); + + if (rn->query) { + ngx_resolver_free(r, rn->query); + } + + ngx_resolver_free(r, rn); + } + + /* unlock addr mutex */ + + if (ctx->event) { + ngx_resolver_free(r, ctx->event); + } + + ngx_resolver_free(r, ctx); + + return NGX_ERROR; +} + + +void +ngx_resolve_addr_done(ngx_resolver_ctx_t *ctx) +{ + ngx_queue_t *expire_queue; + ngx_rbtree_t *tree; + ngx_resolver_t *r; + ngx_resolver_ctx_t *w, **p; + ngx_resolver_node_t *rn; + + r = ctx->resolver; + + switch (ctx->addr.sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + tree = &r->addr6_rbtree; + expire_queue = &r->addr6_expire_queue; + break; +#endif + + default: /* AF_INET */ + tree = &r->addr_rbtree; + expire_queue = &r->addr_expire_queue; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolve addr done: %i", ctx->state); + + if (ctx->event && ctx->event->timer_set) { + ngx_del_timer(ctx->event); + } + + /* lock addr mutex */ + + if (ctx->state == NGX_AGAIN || ctx->state == NGX_RESOLVE_TIMEDOUT) { + + rn = ctx->node; + + if (rn) { + p = &rn->waiting; + w = rn->waiting; + + while (w) { + if (w == ctx) { + *p = w->next; + + goto done; + } + + p = &w->next; + w = w->next; + } + } + + { + u_char text[NGX_SOCKADDR_STRLEN]; + ngx_str_t addrtext; + + addrtext.data = text; + addrtext.len = ngx_sock_ntop(ctx->addr.sockaddr, ctx->addr.socklen, + text, NGX_SOCKADDR_STRLEN, 0); + + ngx_log_error(NGX_LOG_ALERT, r->log, 0, + "could not cancel %V resolving", &addrtext); + } + } + +done: + + ngx_resolver_expire(r, tree, expire_queue); + + /* unlock addr mutex */ + + /* lock alloc mutex */ + + if (ctx->event) { + ngx_resolver_free_locked(r, ctx->event); + } + + ngx_resolver_free_locked(r, ctx); + + /* unlock alloc mutex */ + + if (r->event->timer_set && ngx_resolver_resend_empty(r)) { + ngx_del_timer(r->event); + } +} + + +static void +ngx_resolver_expire(ngx_resolver_t *r, ngx_rbtree_t *tree, ngx_queue_t *queue) +{ + time_t now; + ngx_uint_t i; + ngx_queue_t *q; + ngx_resolver_node_t *rn; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, r->log, 0, "resolver expire"); + + now = ngx_time(); + + for (i = 0; i < 2; i++) { + if (ngx_queue_empty(queue)) { + return; + } + + q = ngx_queue_last(queue); + + rn = ngx_queue_data(q, ngx_resolver_node_t, queue); + + if (now <= rn->expire) { + return; + } + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolver expire \"%*s\"", (size_t) rn->nlen, rn->name); + + ngx_queue_remove(q); + + ngx_rbtree_delete(tree, &rn->node); + + ngx_resolver_free_node(r, rn); + } +} + + +static ngx_int_t +ngx_resolver_send_query(ngx_resolver_t *r, ngx_resolver_node_t *rn) +{ + ssize_t n; + ngx_udp_connection_t *uc; + + uc = r->udp_connections.elts; + + uc = &uc[r->last_connection++]; + if (r->last_connection == r->udp_connections.nelts) { + r->last_connection = 0; + } + + if (uc->connection == NULL) { + + uc->log = *r->log; + uc->log.handler = ngx_resolver_log_error; + uc->log.data = uc; + uc->log.action = "resolving"; + + if (ngx_udp_connect(uc) != NGX_OK) { + return NGX_ERROR; + } + + uc->connection->data = r; + uc->connection->read->handler = ngx_resolver_read_response; + uc->connection->read->resolver = 1; + } + + if (rn->naddrs == (u_short) -1) { + n = ngx_send(uc->connection, rn->query, rn->qlen); + + if (n == -1) { + return NGX_ERROR; + } + + if ((size_t) n != (size_t) rn->qlen) { + ngx_log_error(NGX_LOG_CRIT, &uc->log, 0, "send() incomplete"); + return NGX_ERROR; + } + } + +#if (NGX_HAVE_INET6) + if (rn->query6 && rn->naddrs6 == (u_short) -1) { + n = ngx_send(uc->connection, rn->query6, rn->qlen); + + if (n == -1) { + return NGX_ERROR; + } + + if ((size_t) n != (size_t) rn->qlen) { + ngx_log_error(NGX_LOG_CRIT, &uc->log, 0, "send() incomplete"); + return NGX_ERROR; + } + } +#endif + + return NGX_OK; +} + + +static void +ngx_resolver_resend_handler(ngx_event_t *ev) +{ + time_t timer, atimer, ntimer; +#if (NGX_HAVE_INET6) + time_t a6timer; +#endif + ngx_resolver_t *r; + + r = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolver resend handler"); + + /* lock name mutex */ + + ntimer = ngx_resolver_resend(r, &r->name_rbtree, &r->name_resend_queue); + + /* unlock name mutex */ + + /* lock addr mutex */ + + atimer = ngx_resolver_resend(r, &r->addr_rbtree, &r->addr_resend_queue); + + /* unlock addr mutex */ + +#if (NGX_HAVE_INET6) + + /* lock addr6 mutex */ + + a6timer = ngx_resolver_resend(r, &r->addr6_rbtree, &r->addr6_resend_queue); + + /* unlock addr6 mutex */ + +#endif + + timer = ntimer; + + if (timer == 0) { + timer = atimer; + + } else if (atimer) { + timer = ngx_min(timer, atimer); + } + +#if (NGX_HAVE_INET6) + + if (timer == 0) { + timer = a6timer; + + } else if (a6timer) { + timer = ngx_min(timer, a6timer); + } + +#endif + + if (timer) { + ngx_add_timer(r->event, (ngx_msec_t) (timer * 1000)); + } +} + + +static time_t +ngx_resolver_resend(ngx_resolver_t *r, ngx_rbtree_t *tree, ngx_queue_t *queue) +{ + time_t now; + ngx_queue_t *q; + ngx_resolver_node_t *rn; + + now = ngx_time(); + + for ( ;; ) { + if (ngx_queue_empty(queue)) { + return 0; + } + + q = ngx_queue_last(queue); + + rn = ngx_queue_data(q, ngx_resolver_node_t, queue); + + if (now < rn->expire) { + return rn->expire - now; + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolver resend \"%*s\" %p", + (size_t) rn->nlen, rn->name, rn->waiting); + + ngx_queue_remove(q); + + if (rn->waiting) { + + (void) ngx_resolver_send_query(r, rn); + + rn->expire = now + r->resend_timeout; + + ngx_queue_insert_head(queue, q); + + continue; + } + + ngx_rbtree_delete(tree, &rn->node); + + ngx_resolver_free_node(r, rn); + } +} + + +static ngx_uint_t +ngx_resolver_resend_empty(ngx_resolver_t *r) +{ + return ngx_queue_empty(&r->name_resend_queue) +#if (NGX_HAVE_INET6) + && ngx_queue_empty(&r->addr6_resend_queue) +#endif + && ngx_queue_empty(&r->addr_resend_queue); +} + + +static void +ngx_resolver_read_response(ngx_event_t *rev) +{ + ssize_t n; + ngx_connection_t *c; + u_char buf[NGX_RESOLVER_UDP_SIZE]; + + c = rev->data; + + do { + n = ngx_udp_recv(c, buf, NGX_RESOLVER_UDP_SIZE); + + if (n < 0) { + return; + } + + ngx_resolver_process_response(c->data, buf, n); + + } while (rev->ready); +} + + +static void +ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, size_t n) +{ + char *err; + ngx_uint_t i, times, ident, qident, flags, code, nqs, nan, + qtype, qclass; +#if (NGX_HAVE_INET6) + ngx_uint_t qident6; +#endif + ngx_queue_t *q; + ngx_resolver_qs_t *qs; + ngx_resolver_hdr_t *response; + ngx_resolver_node_t *rn; + + if (n < sizeof(ngx_resolver_hdr_t)) { + goto short_response; + } + + response = (ngx_resolver_hdr_t *) buf; + + ident = (response->ident_hi << 8) + response->ident_lo; + flags = (response->flags_hi << 8) + response->flags_lo; + nqs = (response->nqs_hi << 8) + response->nqs_lo; + nan = (response->nan_hi << 8) + response->nan_lo; + + ngx_log_debug6(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolver DNS response %ui fl:%04Xui %ui/%ui/%ud/%ud", + ident, flags, nqs, nan, + (response->nns_hi << 8) + response->nns_lo, + (response->nar_hi << 8) + response->nar_lo); + + /* response to a standard query */ + if ((flags & 0xf870) != 0x8000) { + ngx_log_error(r->log_level, r->log, 0, + "invalid DNS response %ui fl:%04Xui", ident, flags); + return; + } + + code = flags & 0xf; + + if (code == NGX_RESOLVE_FORMERR) { + + times = 0; + + for (q = ngx_queue_head(&r->name_resend_queue); + q != ngx_queue_sentinel(&r->name_resend_queue) && times++ < 100; + q = ngx_queue_next(q)) + { + rn = ngx_queue_data(q, ngx_resolver_node_t, queue); + qident = (rn->query[0] << 8) + rn->query[1]; + + if (qident == ident) { + goto dns_error_name; + } + +#if (NGX_HAVE_INET6) + if (rn->query6) { + qident6 = (rn->query6[0] << 8) + rn->query6[1]; + + if (qident6 == ident) { + goto dns_error_name; + } + } +#endif + } + + goto dns_error; + } + + if (code > NGX_RESOLVE_REFUSED) { + goto dns_error; + } + + if (nqs != 1) { + err = "invalid number of questions in DNS response"; + goto done; + } + + i = sizeof(ngx_resolver_hdr_t); + + while (i < (ngx_uint_t) n) { + if (buf[i] == '\0') { + goto found; + } + + i += 1 + buf[i]; + } + + goto short_response; + +found: + + if (i++ == sizeof(ngx_resolver_hdr_t)) { + err = "zero-length domain name in DNS response"; + goto done; + } + + if (i + sizeof(ngx_resolver_qs_t) + nan * (2 + sizeof(ngx_resolver_an_t)) + > (ngx_uint_t) n) + { + goto short_response; + } + + qs = (ngx_resolver_qs_t *) &buf[i]; + + qtype = (qs->type_hi << 8) + qs->type_lo; + qclass = (qs->class_hi << 8) + qs->class_lo; + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolver DNS response qt:%ui cl:%ui", qtype, qclass); + + if (qclass != 1) { + ngx_log_error(r->log_level, r->log, 0, + "unknown query class %ui in DNS response", qclass); + return; + } + + switch (qtype) { + + case NGX_RESOLVE_A: +#if (NGX_HAVE_INET6) + case NGX_RESOLVE_AAAA: +#endif + + ngx_resolver_process_a(r, buf, n, ident, code, qtype, nan, + i + sizeof(ngx_resolver_qs_t)); + + break; + + case NGX_RESOLVE_PTR: + + ngx_resolver_process_ptr(r, buf, n, ident, code, nan); + + break; + + default: + ngx_log_error(r->log_level, r->log, 0, + "unknown query type %ui in DNS response", qtype); + return; + } + + return; + +short_response: + + err = "short DNS response"; + +done: + + ngx_log_error(r->log_level, r->log, 0, err); + + return; + +dns_error_name: + + ngx_log_error(r->log_level, r->log, 0, + "DNS error (%ui: %s), query id:%ui, name:\"%*s\"", + code, ngx_resolver_strerror(code), ident, + rn->nlen, rn->name); + return; + +dns_error: + + ngx_log_error(r->log_level, r->log, 0, + "DNS error (%ui: %s), query id:%ui", + code, ngx_resolver_strerror(code), ident); + return; +} + + +static void +ngx_resolver_process_a(ngx_resolver_t *r, u_char *buf, size_t last, + ngx_uint_t ident, ngx_uint_t code, ngx_uint_t qtype, + ngx_uint_t nan, ngx_uint_t ans) +{ + char *err; + u_char *cname; + size_t len; + int32_t ttl; + uint32_t hash; + in_addr_t *addr; + ngx_str_t name; + ngx_addr_t *addrs; + ngx_uint_t type, class, qident, naddrs, a, i, n, start; +#if (NGX_HAVE_INET6) + struct in6_addr *addr6; +#endif + ngx_resolver_an_t *an; + ngx_resolver_ctx_t *ctx, *next; + ngx_resolver_node_t *rn; + + if (ngx_resolver_copy(r, &name, buf, + buf + sizeof(ngx_resolver_hdr_t), buf + last) + != NGX_OK) + { + return; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, "resolver qs:%V", &name); + + hash = ngx_crc32_short(name.data, name.len); + + /* lock name mutex */ + + rn = ngx_resolver_lookup_name(r, &name, hash); + + if (rn == NULL) { + ngx_log_error(r->log_level, r->log, 0, + "unexpected response for %V", &name); + ngx_resolver_free(r, name.data); + goto failed; + } + + switch (qtype) { + +#if (NGX_HAVE_INET6) + case NGX_RESOLVE_AAAA: + + if (rn->query6 == NULL || rn->naddrs6 != (u_short) -1) { + ngx_log_error(r->log_level, r->log, 0, + "unexpected response for %V", &name); + ngx_resolver_free(r, name.data); + goto failed; + } + + qident = (rn->query6[0] << 8) + rn->query6[1]; + + break; +#endif + + default: /* NGX_RESOLVE_A */ + + if (rn->query == NULL || rn->naddrs != (u_short) -1) { + ngx_log_error(r->log_level, r->log, 0, + "unexpected response for %V", &name); + ngx_resolver_free(r, name.data); + goto failed; + } + + qident = (rn->query[0] << 8) + rn->query[1]; + } + + if (ident != qident) { + ngx_log_error(r->log_level, r->log, 0, + "wrong ident %ui response for %V, expect %ui", + ident, &name, qident); + ngx_resolver_free(r, name.data); + goto failed; + } + + ngx_resolver_free(r, name.data); + + if (code == 0 && rn->code) { + code = rn->code; + } + + if (code == 0 && nan == 0) { + +#if (NGX_HAVE_INET6) + switch (qtype) { + + case NGX_RESOLVE_AAAA: + + rn->naddrs6 = 0; + + if (rn->naddrs == (u_short) -1) { + goto next; + } + + if (rn->naddrs) { + goto export; + } + + break; + + default: /* NGX_RESOLVE_A */ + + rn->naddrs = 0; + + if (rn->naddrs6 == (u_short) -1) { + goto next; + } + + if (rn->naddrs6) { + goto export; + } + } +#endif + + code = NGX_RESOLVE_NXDOMAIN; + } + + if (code) { + +#if (NGX_HAVE_INET6) + switch (qtype) { + + case NGX_RESOLVE_AAAA: + + rn->naddrs6 = 0; + + if (rn->naddrs == (u_short) -1) { + rn->code = (u_char) code; + goto next; + } + + break; + + default: /* NGX_RESOLVE_A */ + + rn->naddrs = 0; + + if (rn->naddrs6 == (u_short) -1) { + rn->code = (u_char) code; + goto next; + } + } +#endif + + next = rn->waiting; + rn->waiting = NULL; + + ngx_queue_remove(&rn->queue); + + ngx_rbtree_delete(&r->name_rbtree, &rn->node); + + /* unlock name mutex */ + + while (next) { + ctx = next; + ctx->state = code; + next = ctx->next; + + ctx->handler(ctx); + } + + ngx_resolver_free_node(r, rn); + + return; + } + + i = ans; + naddrs = 0; + cname = NULL; + + for (a = 0; a < nan; a++) { + + start = i; + + while (i < last) { + + if (buf[i] & 0xc0) { + i += 2; + goto found; + } + + if (buf[i] == 0) { + i++; + goto test_length; + } + + i += 1 + buf[i]; + } + + goto short_response; + + test_length: + + if (i - start < 2) { + err = "invalid name in DNS response"; + goto invalid; + } + + found: + + if (i + sizeof(ngx_resolver_an_t) >= last) { + goto short_response; + } + + an = (ngx_resolver_an_t *) &buf[i]; + + type = (an->type_hi << 8) + an->type_lo; + class = (an->class_hi << 8) + an->class_lo; + len = (an->len_hi << 8) + an->len_lo; + ttl = (an->ttl[0] << 24) + (an->ttl[1] << 16) + + (an->ttl[2] << 8) + (an->ttl[3]); + + if (class != 1) { + ngx_log_error(r->log_level, r->log, 0, + "unexpected RR class %ui", class); + goto failed; + } + + if (ttl < 0) { + ttl = 0; + } + + rn->ttl = ngx_min(rn->ttl, (uint32_t) ttl); + + i += sizeof(ngx_resolver_an_t); + + switch (type) { + + case NGX_RESOLVE_A: + + if (qtype != NGX_RESOLVE_A) { + err = "unexpected A record in DNS response"; + goto invalid; + } + + if (len != 4) { + err = "invalid A record in DNS response"; + goto invalid; + } + + if (i + 4 > last) { + goto short_response; + } + + naddrs++; + + break; + +#if (NGX_HAVE_INET6) + case NGX_RESOLVE_AAAA: + + if (qtype != NGX_RESOLVE_AAAA) { + err = "unexpected AAAA record in DNS response"; + goto invalid; + } + + if (len != 16) { + err = "invalid AAAA record in DNS response"; + goto invalid; + } + + if (i + 16 > last) { + goto short_response; + } + + naddrs++; + + break; +#endif + + case NGX_RESOLVE_CNAME: + + cname = &buf[i]; + + break; + + case NGX_RESOLVE_DNAME: + + break; + + default: + + ngx_log_error(r->log_level, r->log, 0, + "unexpected RR type %ui", type); + } + + i += len; + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolver naddrs:%ui cname:%p ttl:%uD", + naddrs, cname, rn->ttl); + + if (naddrs) { + + switch (qtype) { + +#if (NGX_HAVE_INET6) + case NGX_RESOLVE_AAAA: + + if (naddrs == 1) { + addr6 = &rn->u6.addr6; + rn->naddrs6 = 1; + + } else { + addr6 = ngx_resolver_alloc(r, naddrs * sizeof(struct in6_addr)); + if (addr6 == NULL) { + goto failed; + } + + rn->u6.addrs6 = addr6; + rn->naddrs6 = (u_short) naddrs; + } + +#if (NGX_SUPPRESS_WARN) + addr = NULL; +#endif + + break; +#endif + + default: /* NGX_RESOLVE_A */ + + if (naddrs == 1) { + addr = &rn->u.addr; + rn->naddrs = 1; + + } else { + addr = ngx_resolver_alloc(r, naddrs * sizeof(in_addr_t)); + if (addr == NULL) { + goto failed; + } + + rn->u.addrs = addr; + rn->naddrs = (u_short) naddrs; + } + +#if (NGX_HAVE_INET6 && NGX_SUPPRESS_WARN) + addr6 = NULL; +#endif + } + + n = 0; + i = ans; + + for (a = 0; a < nan; a++) { + + for ( ;; ) { + + if (buf[i] & 0xc0) { + i += 2; + break; + } + + if (buf[i] == 0) { + i++; + break; + } + + i += 1 + buf[i]; + } + + an = (ngx_resolver_an_t *) &buf[i]; + + type = (an->type_hi << 8) + an->type_lo; + len = (an->len_hi << 8) + an->len_lo; + + i += sizeof(ngx_resolver_an_t); + + if (type == NGX_RESOLVE_A) { + + addr[n] = htonl((buf[i] << 24) + (buf[i + 1] << 16) + + (buf[i + 2] << 8) + (buf[i + 3])); + + if (++n == naddrs) { + +#if (NGX_HAVE_INET6) + if (rn->naddrs6 == (u_short) -1) { + goto next; + } +#endif + + break; + } + } + +#if (NGX_HAVE_INET6) + else if (type == NGX_RESOLVE_AAAA) { + + ngx_memcpy(addr6[n].s6_addr, &buf[i], 16); + + if (++n == naddrs) { + + if (rn->naddrs == (u_short) -1) { + goto next; + } + + break; + } + } +#endif + + i += len; + } + } + + switch (qtype) { + +#if (NGX_HAVE_INET6) + case NGX_RESOLVE_AAAA: + + if (rn->naddrs6 == (u_short) -1) { + rn->naddrs6 = 0; + } + + break; +#endif + + default: /* NGX_RESOLVE_A */ + + if (rn->naddrs == (u_short) -1) { + rn->naddrs = 0; + } + } + + if (rn->naddrs != (u_short) -1 +#if (NGX_HAVE_INET6) + && rn->naddrs6 != (u_short) -1 +#endif + && rn->naddrs +#if (NGX_HAVE_INET6) + + rn->naddrs6 +#endif + > 0) + { + +#if (NGX_HAVE_INET6) + export: +#endif + + naddrs = rn->naddrs; +#if (NGX_HAVE_INET6) + naddrs += rn->naddrs6; +#endif + + if (naddrs == 1 && rn->naddrs == 1) { + addrs = NULL; + + } else { + addrs = ngx_resolver_export(r, rn, 0); + if (addrs == NULL) { + goto failed; + } + } + + ngx_queue_remove(&rn->queue); + + rn->valid = ngx_time() + (r->valid ? r->valid : (time_t) rn->ttl); + rn->expire = ngx_time() + r->expire; + + ngx_queue_insert_head(&r->name_expire_queue, &rn->queue); + + next = rn->waiting; + rn->waiting = NULL; + + /* unlock name mutex */ + + while (next) { + ctx = next; + ctx->state = NGX_OK; + ctx->naddrs = naddrs; + + if (addrs == NULL) { + ctx->addrs = &ctx->addr; + ctx->addr.sockaddr = (struct sockaddr *) &ctx->sin; + ctx->addr.socklen = sizeof(struct sockaddr_in); + ngx_memzero(&ctx->sin, sizeof(struct sockaddr_in)); + ctx->sin.sin_family = AF_INET; + ctx->sin.sin_addr.s_addr = rn->u.addr; + + } else { + ctx->addrs = addrs; + } + + next = ctx->next; + + ctx->handler(ctx); + } + + if (addrs != NULL) { + ngx_resolver_free(r, addrs->sockaddr); + ngx_resolver_free(r, addrs); + } + + ngx_resolver_free(r, rn->query); + rn->query = NULL; +#if (NGX_HAVE_INET6) + rn->query6 = NULL; +#endif + + return; + } + + if (cname) { + + /* CNAME only */ + + if (rn->naddrs == (u_short) -1 +#if (NGX_HAVE_INET6) + || rn->naddrs6 == (u_short) -1 +#endif + ) + { + goto next; + } + + if (ngx_resolver_copy(r, &name, buf, cname, buf + last) != NGX_OK) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolver cname:\"%V\"", &name); + + ngx_queue_remove(&rn->queue); + + rn->cnlen = (u_short) name.len; + rn->u.cname = name.data; + + rn->valid = ngx_time() + (r->valid ? r->valid : (time_t) rn->ttl); + rn->expire = ngx_time() + r->expire; + + ngx_queue_insert_head(&r->name_expire_queue, &rn->queue); + + ngx_resolver_free(r, rn->query); + rn->query = NULL; +#if (NGX_HAVE_INET6) + rn->query6 = NULL; +#endif + + ctx = rn->waiting; + rn->waiting = NULL; + + if (ctx) { + + if (ctx->recursion++ >= NGX_RESOLVER_MAX_RECURSION) { + + /* unlock name mutex */ + + do { + ctx->state = NGX_RESOLVE_NXDOMAIN; + next = ctx->next; + + ctx->handler(ctx); + + ctx = next; + } while (ctx); + + return; + } + + for (next = ctx; next; next = next->next) { + next->node = NULL; + } + + (void) ngx_resolve_name_locked(r, ctx, &name); + } + + /* unlock name mutex */ + + return; + } + + ngx_log_error(r->log_level, r->log, 0, + "no A or CNAME types in DNS response"); + return; + +short_response: + + err = "short DNS response"; + +invalid: + + /* unlock name mutex */ + + ngx_log_error(r->log_level, r->log, 0, err); + + return; + +failed: + +next: + + /* unlock name mutex */ + + return; +} + + +static void +ngx_resolver_process_ptr(ngx_resolver_t *r, u_char *buf, size_t n, + ngx_uint_t ident, ngx_uint_t code, ngx_uint_t nan) +{ + char *err; + size_t len; + u_char text[NGX_SOCKADDR_STRLEN]; + in_addr_t addr; + int32_t ttl; + ngx_int_t octet; + ngx_str_t name; + ngx_uint_t i, mask, qident, class; + ngx_queue_t *expire_queue; + ngx_rbtree_t *tree; + ngx_resolver_an_t *an; + ngx_resolver_ctx_t *ctx, *next; + ngx_resolver_node_t *rn; +#if (NGX_HAVE_INET6) + uint32_t hash; + ngx_int_t digit; + struct in6_addr addr6; +#endif + + if (ngx_resolver_copy(r, NULL, buf, + buf + sizeof(ngx_resolver_hdr_t), buf + n) + != NGX_OK) + { + return; + } + + /* AF_INET */ + + addr = 0; + i = sizeof(ngx_resolver_hdr_t); + + for (mask = 0; mask < 32; mask += 8) { + len = buf[i++]; + + octet = ngx_atoi(&buf[i], len); + if (octet == NGX_ERROR || octet > 255) { + goto invalid_in_addr_arpa; + } + + addr += octet << mask; + i += len; + } + + if (ngx_strcasecmp(&buf[i], (u_char *) "\7in-addr\4arpa") == 0) { + i += sizeof("\7in-addr\4arpa"); + + /* lock addr mutex */ + + rn = ngx_resolver_lookup_addr(r, addr); + + tree = &r->addr_rbtree; + expire_queue = &r->addr_expire_queue; + + addr = htonl(addr); + name.len = ngx_inet_ntop(AF_INET, &addr, text, NGX_SOCKADDR_STRLEN); + name.data = text; + + goto valid; + } + +invalid_in_addr_arpa: + +#if (NGX_HAVE_INET6) + + i = sizeof(ngx_resolver_hdr_t); + + for (octet = 15; octet >= 0; octet--) { + if (buf[i++] != '\1') { + goto invalid_ip6_arpa; + } + + digit = ngx_hextoi(&buf[i++], 1); + if (digit == NGX_ERROR) { + goto invalid_ip6_arpa; + } + + addr6.s6_addr[octet] = (u_char) digit; + + if (buf[i++] != '\1') { + goto invalid_ip6_arpa; + } + + digit = ngx_hextoi(&buf[i++], 1); + if (digit == NGX_ERROR) { + goto invalid_ip6_arpa; + } + + addr6.s6_addr[octet] += (u_char) (digit * 16); + } + + if (ngx_strcasecmp(&buf[i], (u_char *) "\3ip6\4arpa") == 0) { + i += sizeof("\3ip6\4arpa"); + + /* lock addr mutex */ + + hash = ngx_crc32_short(addr6.s6_addr, 16); + rn = ngx_resolver_lookup_addr6(r, &addr6, hash); + + tree = &r->addr6_rbtree; + expire_queue = &r->addr6_expire_queue; + + name.len = ngx_inet6_ntop(addr6.s6_addr, text, NGX_SOCKADDR_STRLEN); + name.data = text; + + goto valid; + } + +invalid_ip6_arpa: +#endif + + ngx_log_error(r->log_level, r->log, 0, + "invalid in-addr.arpa or ip6.arpa name in DNS response"); + return; + +valid: + + if (rn == NULL || rn->query == NULL) { + ngx_log_error(r->log_level, r->log, 0, + "unexpected response for %V", &name); + goto failed; + } + + qident = (rn->query[0] << 8) + rn->query[1]; + + if (ident != qident) { + ngx_log_error(r->log_level, r->log, 0, + "wrong ident %ui response for %V, expect %ui", + ident, &name, qident); + goto failed; + } + + if (code == 0 && nan == 0) { + code = NGX_RESOLVE_NXDOMAIN; + } + + if (code) { + next = rn->waiting; + rn->waiting = NULL; + + ngx_queue_remove(&rn->queue); + + ngx_rbtree_delete(tree, &rn->node); + + /* unlock addr mutex */ + + while (next) { + ctx = next; + ctx->state = code; + next = ctx->next; + + ctx->handler(ctx); + } + + ngx_resolver_free_node(r, rn); + + return; + } + + i += sizeof(ngx_resolver_qs_t); + + if (i + 2 + sizeof(ngx_resolver_an_t) >= n) { + goto short_response; + } + + /* compression pointer to *.arpa */ + + if (buf[i] != 0xc0 || buf[i + 1] != sizeof(ngx_resolver_hdr_t)) { + err = "invalid in-addr.arpa or ip6.arpa name in DNS response"; + goto invalid; + } + + an = (ngx_resolver_an_t *) &buf[i + 2]; + + class = (an->class_hi << 8) + an->class_lo; + len = (an->len_hi << 8) + an->len_lo; + ttl = (an->ttl[0] << 24) + (an->ttl[1] << 16) + + (an->ttl[2] << 8) + (an->ttl[3]); + + if (class != 1) { + ngx_log_error(r->log_level, r->log, 0, + "unexpected RR class %ui", class); + goto failed; + } + + if (ttl < 0) { + ttl = 0; + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolver qt:%ui cl:%ui len:%uz", + (an->type_hi << 8) + an->type_lo, + class, len); + + i += 2 + sizeof(ngx_resolver_an_t); + + if (i + len > n) { + goto short_response; + } + + if (ngx_resolver_copy(r, &name, buf, buf + i, buf + n) != NGX_OK) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, r->log, 0, "resolver an:%V", &name); + + if (name.len != (size_t) rn->nlen + || ngx_strncmp(name.data, rn->name, name.len) != 0) + { + if (rn->nlen) { + ngx_resolver_free(r, rn->name); + } + + rn->nlen = (u_short) name.len; + rn->name = name.data; + + name.data = ngx_resolver_dup(r, rn->name, name.len); + if (name.data == NULL) { + goto failed; + } + } + + ngx_queue_remove(&rn->queue); + + rn->valid = ngx_time() + (r->valid ? r->valid : ttl); + rn->expire = ngx_time() + r->expire; + + ngx_queue_insert_head(expire_queue, &rn->queue); + + next = rn->waiting; + rn->waiting = NULL; + + /* unlock addr mutex */ + + while (next) { + ctx = next; + ctx->state = NGX_OK; + ctx->name = name; + next = ctx->next; + + ctx->handler(ctx); + } + + ngx_resolver_free(r, name.data); + + return; + +short_response: + + err = "short DNS response"; + +invalid: + + /* unlock addr mutex */ + + ngx_log_error(r->log_level, r->log, 0, err); + + return; + +failed: + + /* unlock addr mutex */ + + return; +} + + +static ngx_resolver_node_t * +ngx_resolver_lookup_name(ngx_resolver_t *r, ngx_str_t *name, uint32_t hash) +{ + ngx_int_t rc; + ngx_rbtree_node_t *node, *sentinel; + ngx_resolver_node_t *rn; + + node = r->name_rbtree.root; + sentinel = r->name_rbtree.sentinel; + + while (node != sentinel) { + + if (hash < node->key) { + node = node->left; + continue; + } + + if (hash > node->key) { + node = node->right; + continue; + } + + /* hash == node->key */ + + rn = ngx_resolver_node(node); + + rc = ngx_memn2cmp(name->data, rn->name, name->len, rn->nlen); + + if (rc == 0) { + return rn; + } + + node = (rc < 0) ? node->left : node->right; + } + + /* not found */ + + return NULL; +} + + +static ngx_resolver_node_t * +ngx_resolver_lookup_addr(ngx_resolver_t *r, in_addr_t addr) +{ + ngx_rbtree_node_t *node, *sentinel; + + node = r->addr_rbtree.root; + sentinel = r->addr_rbtree.sentinel; + + while (node != sentinel) { + + if (addr < node->key) { + node = node->left; + continue; + } + + if (addr > node->key) { + node = node->right; + continue; + } + + /* addr == node->key */ + + return ngx_resolver_node(node); + } + + /* not found */ + + return NULL; +} + + +#if (NGX_HAVE_INET6) + +static ngx_resolver_node_t * +ngx_resolver_lookup_addr6(ngx_resolver_t *r, struct in6_addr *addr, + uint32_t hash) +{ + ngx_int_t rc; + ngx_rbtree_node_t *node, *sentinel; + ngx_resolver_node_t *rn; + + node = r->addr6_rbtree.root; + sentinel = r->addr6_rbtree.sentinel; + + while (node != sentinel) { + + if (hash < node->key) { + node = node->left; + continue; + } + + if (hash > node->key) { + node = node->right; + continue; + } + + /* hash == node->key */ + + rn = ngx_resolver_node(node); + + rc = ngx_memcmp(addr, &rn->addr6, 16); + + if (rc == 0) { + return rn; + } + + node = (rc < 0) ? node->left : node->right; + } + + /* not found */ + + return NULL; +} + +#endif + + +static void +ngx_resolver_rbtree_insert_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_resolver_node_t *rn, *rn_temp; + + for ( ;; ) { + + if (node->key < temp->key) { + + p = &temp->left; + + } else if (node->key > temp->key) { + + p = &temp->right; + + } else { /* node->key == temp->key */ + + rn = ngx_resolver_node(node); + rn_temp = ngx_resolver_node(temp); + + p = (ngx_memn2cmp(rn->name, rn_temp->name, rn->nlen, rn_temp->nlen) + < 0) ? &temp->left : &temp->right; + } + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +#if (NGX_HAVE_INET6) + +static void +ngx_resolver_rbtree_insert_addr6_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_resolver_node_t *rn, *rn_temp; + + for ( ;; ) { + + if (node->key < temp->key) { + + p = &temp->left; + + } else if (node->key > temp->key) { + + p = &temp->right; + + } else { /* node->key == temp->key */ + + rn = ngx_resolver_node(node); + rn_temp = ngx_resolver_node(temp); + + p = (ngx_memcmp(&rn->addr6, &rn_temp->addr6, 16) + < 0) ? &temp->left : &temp->right; + } + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + +#endif + + +static ngx_int_t +ngx_resolver_create_name_query(ngx_resolver_t *r, ngx_resolver_node_t *rn, + ngx_str_t *name) +{ + u_char *p, *s; + size_t len, nlen; + ngx_uint_t ident; + ngx_resolver_qs_t *qs; + ngx_resolver_hdr_t *query; + + nlen = name->len ? (1 + name->len + 1) : 1; + + len = sizeof(ngx_resolver_hdr_t) + nlen + sizeof(ngx_resolver_qs_t); + +#if (NGX_HAVE_INET6) + p = ngx_resolver_alloc(r, r->ipv6 ? len * 2 : len); +#else + p = ngx_resolver_alloc(r, len); +#endif + if (p == NULL) { + return NGX_ERROR; + } + + rn->qlen = (u_short) len; + rn->query = p; + +#if (NGX_HAVE_INET6) + if (r->ipv6) { + rn->query6 = p + len; + } +#endif + + query = (ngx_resolver_hdr_t *) p; + + ident = ngx_random(); + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolve: \"%V\" A %i", name, ident & 0xffff); + + query->ident_hi = (u_char) ((ident >> 8) & 0xff); + query->ident_lo = (u_char) (ident & 0xff); + + /* recursion query */ + query->flags_hi = 1; query->flags_lo = 0; + + /* one question */ + query->nqs_hi = 0; query->nqs_lo = 1; + query->nan_hi = 0; query->nan_lo = 0; + query->nns_hi = 0; query->nns_lo = 0; + query->nar_hi = 0; query->nar_lo = 0; + + p += sizeof(ngx_resolver_hdr_t) + nlen; + + qs = (ngx_resolver_qs_t *) p; + + /* query type */ + qs->type_hi = 0; qs->type_lo = NGX_RESOLVE_A; + + /* IN query class */ + qs->class_hi = 0; qs->class_lo = 1; + + /* convert "www.example.com" to "\3www\7example\3com\0" */ + + len = 0; + p--; + *p-- = '\0'; + + if (name->len == 0) { + return NGX_DECLINED; + } + + for (s = name->data + name->len - 1; s >= name->data; s--) { + if (*s != '.') { + *p = *s; + len++; + + } else { + if (len == 0 || len > 255) { + return NGX_DECLINED; + } + + *p = (u_char) len; + len = 0; + } + + p--; + } + + if (len == 0 || len > 255) { + return NGX_DECLINED; + } + + *p = (u_char) len; + +#if (NGX_HAVE_INET6) + if (!r->ipv6) { + return NGX_OK; + } + + p = rn->query6; + + ngx_memcpy(p, rn->query, rn->qlen); + + query = (ngx_resolver_hdr_t *) p; + + ident = ngx_random(); + + ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolve: \"%V\" AAAA %i", name, ident & 0xffff); + + query->ident_hi = (u_char) ((ident >> 8) & 0xff); + query->ident_lo = (u_char) (ident & 0xff); + + p += sizeof(ngx_resolver_hdr_t) + nlen; + + qs = (ngx_resolver_qs_t *) p; + + qs->type_lo = NGX_RESOLVE_AAAA; +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_resolver_create_addr_query(ngx_resolver_t *r, ngx_resolver_node_t *rn, + ngx_addr_t *addr) +{ + u_char *p, *d; + size_t len; + in_addr_t inaddr; + ngx_int_t n; + ngx_uint_t ident; + ngx_resolver_hdr_t *query; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + switch (addr->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + len = sizeof(ngx_resolver_hdr_t) + + 64 + sizeof(".ip6.arpa.") - 1 + + sizeof(ngx_resolver_qs_t); + + break; +#endif + + default: /* AF_INET */ + len = sizeof(ngx_resolver_hdr_t) + + sizeof(".255.255.255.255.in-addr.arpa.") - 1 + + sizeof(ngx_resolver_qs_t); + } + + p = ngx_resolver_alloc(r, len); + if (p == NULL) { + return NGX_ERROR; + } + + rn->query = p; + query = (ngx_resolver_hdr_t *) p; + + ident = ngx_random(); + + query->ident_hi = (u_char) ((ident >> 8) & 0xff); + query->ident_lo = (u_char) (ident & 0xff); + + /* recursion query */ + query->flags_hi = 1; query->flags_lo = 0; + + /* one question */ + query->nqs_hi = 0; query->nqs_lo = 1; + query->nan_hi = 0; query->nan_lo = 0; + query->nns_hi = 0; query->nns_lo = 0; + query->nar_hi = 0; query->nar_lo = 0; + + p += sizeof(ngx_resolver_hdr_t); + + switch (addr->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) addr->sockaddr; + + for (n = 15; n >= 0; n--) { + p = ngx_sprintf(p, "\1%xd\1%xd", + sin6->sin6_addr.s6_addr[n] & 0xf, + (sin6->sin6_addr.s6_addr[n] >> 4) & 0xf); + } + + p = ngx_cpymem(p, "\3ip6\4arpa\0", 10); + + break; +#endif + + default: /* AF_INET */ + + sin = (struct sockaddr_in *) addr->sockaddr; + inaddr = ntohl(sin->sin_addr.s_addr); + + for (n = 0; n < 32; n += 8) { + d = ngx_sprintf(&p[1], "%ud", (inaddr >> n) & 0xff); + *p = (u_char) (d - &p[1]); + p = d; + } + + p = ngx_cpymem(p, "\7in-addr\4arpa\0", 14); + } + + /* query type "PTR", IN query class */ + p = ngx_cpymem(p, "\0\14\0\1", 4); + + rn->qlen = (u_short) (p - rn->query); + + return NGX_OK; +} + + +static ngx_int_t +ngx_resolver_copy(ngx_resolver_t *r, ngx_str_t *name, u_char *buf, u_char *src, + u_char *last) +{ + char *err; + u_char *p, *dst; + ssize_t len; + ngx_uint_t i, n; + + p = src; + len = -1; + + /* + * compression pointers allow to create endless loop, so we set limit; + * 128 pointers should be enough to store 255-byte name + */ + + for (i = 0; i < 128; i++) { + n = *p++; + + if (n == 0) { + goto done; + } + + if (n & 0xc0) { + n = ((n & 0x3f) << 8) + *p; + p = &buf[n]; + + } else { + len += 1 + n; + p = &p[n]; + } + + if (p >= last) { + err = "name is out of response"; + goto invalid; + } + } + + err = "compression pointers loop"; + +invalid: + + ngx_log_error(r->log_level, r->log, 0, err); + + return NGX_ERROR; + +done: + + if (name == NULL) { + return NGX_OK; + } + + if (len == -1) { + ngx_str_null(name); + return NGX_OK; + } + + dst = ngx_resolver_alloc(r, len); + if (dst == NULL) { + return NGX_ERROR; + } + + name->data = dst; + + n = *src++; + + for ( ;; ) { + if (n & 0xc0) { + n = ((n & 0x3f) << 8) + *src; + src = &buf[n]; + + n = *src++; + + } else { + ngx_strlow(dst, src, n); + dst += n; + src += n; + + n = *src++; + + if (n != 0) { + *dst++ = '.'; + } + } + + if (n == 0) { + name->len = dst - name->data; + return NGX_OK; + } + } +} + + +static void +ngx_resolver_timeout_handler(ngx_event_t *ev) +{ + ngx_resolver_ctx_t *ctx; + + ctx = ev->data; + + ctx->state = NGX_RESOLVE_TIMEDOUT; + + ctx->handler(ctx); +} + + +static void +ngx_resolver_free_node(ngx_resolver_t *r, ngx_resolver_node_t *rn) +{ + /* lock alloc mutex */ + + if (rn->query) { + ngx_resolver_free_locked(r, rn->query); + } + + if (rn->name) { + ngx_resolver_free_locked(r, rn->name); + } + + if (rn->cnlen) { + ngx_resolver_free_locked(r, rn->u.cname); + } + + if (rn->naddrs > 1 && rn->naddrs != (u_short) -1) { + ngx_resolver_free_locked(r, rn->u.addrs); + } + +#if (NGX_HAVE_INET6) + if (rn->naddrs6 > 1 && rn->naddrs6 != (u_short) -1) { + ngx_resolver_free_locked(r, rn->u6.addrs6); + } +#endif + + ngx_resolver_free_locked(r, rn); + + /* unlock alloc mutex */ +} + + +static void * +ngx_resolver_alloc(ngx_resolver_t *r, size_t size) +{ + u_char *p; + + /* lock alloc mutex */ + + p = ngx_alloc(size, r->log); + + /* unlock alloc mutex */ + + return p; +} + + +static void * +ngx_resolver_calloc(ngx_resolver_t *r, size_t size) +{ + u_char *p; + + p = ngx_resolver_alloc(r, size); + + if (p) { + ngx_memzero(p, size); + } + + return p; +} + + +static void +ngx_resolver_free(ngx_resolver_t *r, void *p) +{ + /* lock alloc mutex */ + + ngx_free(p); + + /* unlock alloc mutex */ +} + + +static void +ngx_resolver_free_locked(ngx_resolver_t *r, void *p) +{ + ngx_free(p); +} + + +static void * +ngx_resolver_dup(ngx_resolver_t *r, void *src, size_t size) +{ + void *dst; + + dst = ngx_resolver_alloc(r, size); + + if (dst == NULL) { + return dst; + } + + ngx_memcpy(dst, src, size); + + return dst; +} + + +static ngx_addr_t * +ngx_resolver_export(ngx_resolver_t *r, ngx_resolver_node_t *rn, + ngx_uint_t rotate) +{ + ngx_addr_t *dst; + ngx_uint_t d, i, j, n; + u_char (*sockaddr)[NGX_SOCKADDRLEN]; + in_addr_t *addr; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct in6_addr *addr6; + struct sockaddr_in6 *sin6; +#endif + + n = rn->naddrs; +#if (NGX_HAVE_INET6) + n += rn->naddrs6; +#endif + + dst = ngx_resolver_calloc(r, n * sizeof(ngx_addr_t)); + if (dst == NULL) { + return NULL; + } + + sockaddr = ngx_resolver_calloc(r, n * NGX_SOCKADDRLEN); + if (sockaddr == NULL) { + ngx_resolver_free(r, dst); + return NULL; + } + + i = 0; + d = rotate ? ngx_random() % n : 0; + + if (rn->naddrs) { + j = rotate ? ngx_random() % rn->naddrs : 0; + + addr = (rn->naddrs == 1) ? &rn->u.addr : rn->u.addrs; + + do { + sin = (struct sockaddr_in *) sockaddr[d]; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = addr[j++]; + dst[d].sockaddr = (struct sockaddr *) sin; + dst[d++].socklen = sizeof(struct sockaddr_in); + + if (d == n) { + d = 0; + } + + if (j == rn->naddrs) { + j = 0; + } + } while (++i < rn->naddrs); + } + +#if (NGX_HAVE_INET6) + if (rn->naddrs6) { + j = rotate ? ngx_random() % rn->naddrs6 : 0; + + addr6 = (rn->naddrs6 == 1) ? &rn->u6.addr6 : rn->u6.addrs6; + + do { + sin6 = (struct sockaddr_in6 *) sockaddr[d]; + sin6->sin6_family = AF_INET6; + ngx_memcpy(sin6->sin6_addr.s6_addr, addr6[j++].s6_addr, 16); + dst[d].sockaddr = (struct sockaddr *) sin6; + dst[d++].socklen = sizeof(struct sockaddr_in6); + + if (d == n) { + d = 0; + } + + if (j == rn->naddrs6) { + j = 0; + } + } while (++i < n); + } +#endif + + return dst; +} + + +char * +ngx_resolver_strerror(ngx_int_t err) +{ + static char *errors[] = { + "Format error", /* FORMERR */ + "Server failure", /* SERVFAIL */ + "Host not found", /* NXDOMAIN */ + "Unimplemented", /* NOTIMP */ + "Operation refused" /* REFUSED */ + }; + + if (err > 0 && err < 6) { + return errors[err - 1]; + } + + if (err == NGX_RESOLVE_TIMEDOUT) { + return "Operation timed out"; + } + + return "Unknown error"; +} + + +static u_char * +ngx_resolver_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + u_char *p; + ngx_udp_connection_t *uc; + + p = buf; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + } + + uc = log->data; + + if (uc) { + p = ngx_snprintf(p, len, ", resolver: %V", &uc->server); + } + + return p; +} + + +ngx_int_t +ngx_udp_connect(ngx_udp_connection_t *uc) +{ + int rc; + ngx_int_t event; + ngx_event_t *rev, *wev; + ngx_socket_t s; + ngx_connection_t *c; + + s = ngx_socket(uc->sockaddr->sa_family, SOCK_DGRAM, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &uc->log, 0, "UDP socket %d", s); + + if (s == (ngx_socket_t) -1) { + ngx_log_error(NGX_LOG_ALERT, &uc->log, ngx_socket_errno, + ngx_socket_n " failed"); + return NGX_ERROR; + } + + c = ngx_get_connection(s, &uc->log); + + if (c == NULL) { + if (ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_ALERT, &uc->log, ngx_socket_errno, + ngx_close_socket_n "failed"); + } + + return NGX_ERROR; + } + + if (ngx_nonblocking(s) == -1) { + ngx_log_error(NGX_LOG_ALERT, &uc->log, ngx_socket_errno, + ngx_nonblocking_n " failed"); + + goto failed; + } + + rev = c->read; + wev = c->write; + + rev->log = &uc->log; + wev->log = &uc->log; + + uc->connection = c; + + c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, &uc->log, 0, + "connect to %V, fd:%d #%uA", &uc->server, s, c->number); + + rc = connect(s, uc->sockaddr, uc->socklen); + + /* TODO: iocp */ + + if (rc == -1) { + ngx_log_error(NGX_LOG_CRIT, &uc->log, ngx_socket_errno, + "connect() failed"); + + goto failed; + } + + /* UDP sockets are always ready to write */ + wev->ready = 1; + + event = (ngx_event_flags & NGX_USE_CLEAR_EVENT) ? + /* kqueue, epoll */ NGX_CLEAR_EVENT: + /* select, poll, /dev/poll */ NGX_LEVEL_EVENT; + /* eventport event type has no meaning: oneshot only */ + + if (ngx_add_event(rev, NGX_READ_EVENT, event) != NGX_OK) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_close_connection(c); + uc->connection = NULL; + + return NGX_ERROR; +} From 5277cdb1430f0b0c8cb03893fa8c5310383fd3c7 Mon Sep 17 00:00:00 2001 From: turly221 Date: Wed, 11 Dec 2024 15:16:12 +0000 Subject: [PATCH 4/4] commit patch 18526596 --- src/http/modules/ngx_http_mp4_module.c | 147 + src/http/modules/ngx_http_mp4_module.c.orig | 3500 +++++++++++++++++++ 2 files changed, 3647 insertions(+) create mode 100644 src/http/modules/ngx_http_mp4_module.c.orig diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c index 980bf57191..b2419be517 100644 --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -1016,6 +1016,12 @@ ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) return NGX_ERROR; } + if (mp4->ftyp_atom.buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 ftyp atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; ftyp_atom = ngx_palloc(mp4->request->pool, atom_size); @@ -1074,6 +1080,12 @@ ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) return NGX_DECLINED; } + if (mp4->moov_atom.buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 moov atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module); if (atom_data_size > mp4->buffer_size) { @@ -1141,6 +1153,12 @@ ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdat atom"); + if (mp4->mdat_atom.buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 mdat atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + data = &mp4->mdat_data_buf; data->file = &mp4->file; data->in_file = 1; @@ -1265,6 +1283,12 @@ ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mvhd atom"); + if (mp4->mvhd_atom.buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 mvhd atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + atom_header = ngx_mp4_atom_header(mp4); mvhd_atom = (ngx_mp4_mvhd_atom_t *) atom_header; mvhd64_atom = (ngx_mp4_mvhd64_atom_t *) atom_header; @@ -1530,6 +1554,13 @@ ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_TKHD_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 tkhd atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + trak->tkhd_size = atom_size; ngx_mp4_set_32value(tkhd_atom->size, atom_size); @@ -1568,6 +1599,12 @@ ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak = ngx_mp4_last_trak(mp4); + if (trak->out[NGX_HTTP_MP4_MDIA_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 mdia atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + atom = &trak->mdia_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -1691,6 +1728,13 @@ ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 mdhd atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + trak->mdhd_size = atom_size; trak->timescale = timescale; @@ -1733,6 +1777,12 @@ ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak = ngx_mp4_last_trak(mp4); + if (trak->out[NGX_HTTP_MP4_HDLR_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 hdlr atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + atom = &trak->hdlr_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -1761,6 +1811,12 @@ ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak = ngx_mp4_last_trak(mp4); + if (trak->out[NGX_HTTP_MP4_MINF_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 minf atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + atom = &trak->minf_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -1804,6 +1860,15 @@ ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak = ngx_mp4_last_trak(mp4); + if (trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf + || trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 vmhd/smhd atom in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + atom = &trak->vmhd_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -1835,6 +1900,15 @@ ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak = ngx_mp4_last_trak(mp4); + if (trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf + || trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 vmhd/smhd atom in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + atom = &trak->smhd_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -1866,6 +1940,12 @@ ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak = ngx_mp4_last_trak(mp4); + if (trak->out[NGX_HTTP_MP4_DINF_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 dinf atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + atom = &trak->dinf_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -1894,6 +1974,12 @@ ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak = ngx_mp4_last_trak(mp4); + if (trak->out[NGX_HTTP_MP4_STBL_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 stbl atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + atom = &trak->stbl_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -1962,6 +2048,12 @@ ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) trak = ngx_mp4_last_trak(mp4); + if (trak->out[NGX_HTTP_MP4_STSD_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 stsd atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + atom = &trak->stsd_atom_buf; atom->temporary = 1; atom->pos = atom_header; @@ -2030,6 +2122,13 @@ ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) atom_end = atom_table + entries * sizeof(ngx_mp4_stts_entry_t); trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_STTS_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 stts atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + trak->time_to_sample_entries = entries; atom = &trak->stts_atom_buf; @@ -2235,6 +2334,13 @@ ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) "sync sample entries:%uD", entries); trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_STSS_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 stss atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + trak->sync_samples_entries = entries; atom_table = atom_header + sizeof(ngx_http_mp4_stss_atom_t); @@ -2433,6 +2539,13 @@ ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) "composition offset entries:%uD", entries); trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 ctts atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + trak->composition_offset_entries = entries; atom_table = atom_header + sizeof(ngx_mp4_ctts_atom_t); @@ -2636,6 +2749,13 @@ ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) atom_end = atom_table + entries * sizeof(ngx_mp4_stsc_entry_t); trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_STSC_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 stsc atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + trak->sample_to_chunk_entries = entries; atom = &trak->stsc_atom_buf; @@ -2968,6 +3088,13 @@ ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) "sample uniform size:%uD, entries:%uD", size, entries); trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 stsz atom in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + trak->sample_sizes_entries = entries; atom_table = atom_header + sizeof(ngx_mp4_stsz_atom_t); @@ -3137,6 +3264,16 @@ ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) atom_end = atom_table + entries * sizeof(uint32_t); trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_STCO_ATOM].buf + || trak->out[NGX_HTTP_MP4_CO64_ATOM].buf) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 stco/co64 atom in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + trak->chunks = entries; atom = &trak->stco_atom_buf; @@ -3321,6 +3458,16 @@ ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) atom_end = atom_table + entries * sizeof(uint64_t); trak = ngx_mp4_last_trak(mp4); + + if (trak->out[NGX_HTTP_MP4_STCO_ATOM].buf + || trak->out[NGX_HTTP_MP4_CO64_ATOM].buf) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "duplicate mp4 stco/co64 atom in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + trak->chunks = entries; atom = &trak->co64_atom_buf; diff --git a/src/http/modules/ngx_http_mp4_module.c.orig b/src/http/modules/ngx_http_mp4_module.c.orig new file mode 100644 index 0000000000..980bf57191 --- /dev/null +++ b/src/http/modules/ngx_http_mp4_module.c.orig @@ -0,0 +1,3500 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + +#include +#include +#include + + +#define NGX_HTTP_MP4_TRAK_ATOM 0 +#define NGX_HTTP_MP4_TKHD_ATOM 1 +#define NGX_HTTP_MP4_MDIA_ATOM 2 +#define NGX_HTTP_MP4_MDHD_ATOM 3 +#define NGX_HTTP_MP4_HDLR_ATOM 4 +#define NGX_HTTP_MP4_MINF_ATOM 5 +#define NGX_HTTP_MP4_VMHD_ATOM 6 +#define NGX_HTTP_MP4_SMHD_ATOM 7 +#define NGX_HTTP_MP4_DINF_ATOM 8 +#define NGX_HTTP_MP4_STBL_ATOM 9 +#define NGX_HTTP_MP4_STSD_ATOM 10 +#define NGX_HTTP_MP4_STTS_ATOM 11 +#define NGX_HTTP_MP4_STTS_DATA 12 +#define NGX_HTTP_MP4_STSS_ATOM 13 +#define NGX_HTTP_MP4_STSS_DATA 14 +#define NGX_HTTP_MP4_CTTS_ATOM 15 +#define NGX_HTTP_MP4_CTTS_DATA 16 +#define NGX_HTTP_MP4_STSC_ATOM 17 +#define NGX_HTTP_MP4_STSC_START 18 +#define NGX_HTTP_MP4_STSC_DATA 19 +#define NGX_HTTP_MP4_STSC_END 20 +#define NGX_HTTP_MP4_STSZ_ATOM 21 +#define NGX_HTTP_MP4_STSZ_DATA 22 +#define NGX_HTTP_MP4_STCO_ATOM 23 +#define NGX_HTTP_MP4_STCO_DATA 24 +#define NGX_HTTP_MP4_CO64_ATOM 25 +#define NGX_HTTP_MP4_CO64_DATA 26 + +#define NGX_HTTP_MP4_LAST_ATOM NGX_HTTP_MP4_CO64_DATA + + +typedef struct { + size_t buffer_size; + size_t max_buffer_size; +} ngx_http_mp4_conf_t; + + +typedef struct { + u_char chunk[4]; + u_char samples[4]; + u_char id[4]; +} ngx_mp4_stsc_entry_t; + + +typedef struct { + uint32_t timescale; + uint32_t time_to_sample_entries; + uint32_t sample_to_chunk_entries; + uint32_t sync_samples_entries; + uint32_t composition_offset_entries; + uint32_t sample_sizes_entries; + uint32_t chunks; + + ngx_uint_t start_sample; + ngx_uint_t end_sample; + ngx_uint_t start_chunk; + ngx_uint_t end_chunk; + ngx_uint_t start_chunk_samples; + ngx_uint_t end_chunk_samples; + uint64_t start_chunk_samples_size; + uint64_t end_chunk_samples_size; + off_t start_offset; + off_t end_offset; + + size_t tkhd_size; + size_t mdhd_size; + size_t hdlr_size; + size_t vmhd_size; + size_t smhd_size; + size_t dinf_size; + size_t size; + + ngx_chain_t out[NGX_HTTP_MP4_LAST_ATOM + 1]; + + ngx_buf_t trak_atom_buf; + ngx_buf_t tkhd_atom_buf; + ngx_buf_t mdia_atom_buf; + ngx_buf_t mdhd_atom_buf; + ngx_buf_t hdlr_atom_buf; + ngx_buf_t minf_atom_buf; + ngx_buf_t vmhd_atom_buf; + ngx_buf_t smhd_atom_buf; + ngx_buf_t dinf_atom_buf; + ngx_buf_t stbl_atom_buf; + ngx_buf_t stsd_atom_buf; + ngx_buf_t stts_atom_buf; + ngx_buf_t stts_data_buf; + ngx_buf_t stss_atom_buf; + ngx_buf_t stss_data_buf; + ngx_buf_t ctts_atom_buf; + ngx_buf_t ctts_data_buf; + ngx_buf_t stsc_atom_buf; + ngx_buf_t stsc_start_chunk_buf; + ngx_buf_t stsc_end_chunk_buf; + ngx_buf_t stsc_data_buf; + ngx_buf_t stsz_atom_buf; + ngx_buf_t stsz_data_buf; + ngx_buf_t stco_atom_buf; + ngx_buf_t stco_data_buf; + ngx_buf_t co64_atom_buf; + ngx_buf_t co64_data_buf; + + ngx_mp4_stsc_entry_t stsc_start_chunk_entry; + ngx_mp4_stsc_entry_t stsc_end_chunk_entry; +} ngx_http_mp4_trak_t; + + +typedef struct { + ngx_file_t file; + + u_char *buffer; + u_char *buffer_start; + u_char *buffer_pos; + u_char *buffer_end; + size_t buffer_size; + + off_t offset; + off_t end; + off_t content_length; + ngx_uint_t start; + ngx_uint_t length; + uint32_t timescale; + ngx_http_request_t *request; + ngx_array_t trak; + ngx_http_mp4_trak_t traks[2]; + + size_t ftyp_size; + size_t moov_size; + + ngx_chain_t *out; + ngx_chain_t ftyp_atom; + ngx_chain_t moov_atom; + ngx_chain_t mvhd_atom; + ngx_chain_t mdat_atom; + ngx_chain_t mdat_data; + + ngx_buf_t ftyp_atom_buf; + ngx_buf_t moov_atom_buf; + ngx_buf_t mvhd_atom_buf; + ngx_buf_t mdat_atom_buf; + ngx_buf_t mdat_data_buf; + + u_char moov_atom_header[8]; + u_char mdat_atom_header[16]; +} ngx_http_mp4_file_t; + + +typedef struct { + char *name; + ngx_int_t (*handler)(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +} ngx_http_mp4_atom_handler_t; + + +#define ngx_mp4_atom_header(mp4) (mp4->buffer_pos - 8) +#define ngx_mp4_atom_data(mp4) mp4->buffer_pos +#define ngx_mp4_atom_data_size(t) (uint64_t) (sizeof(t) - 8) + + +#define ngx_mp4_atom_next(mp4, n) \ + mp4->buffer_pos += (size_t) n; \ + mp4->offset += n + + +#define ngx_mp4_set_atom_name(p, n1, n2, n3, n4) \ + ((u_char *) (p))[4] = n1; \ + ((u_char *) (p))[5] = n2; \ + ((u_char *) (p))[6] = n3; \ + ((u_char *) (p))[7] = n4 + +#define ngx_mp4_get_32value(p) \ + ( ((uint32_t) ((u_char *) (p))[0] << 24) \ + + ( ((u_char *) (p))[1] << 16) \ + + ( ((u_char *) (p))[2] << 8) \ + + ( ((u_char *) (p))[3]) ) + +#define ngx_mp4_set_32value(p, n) \ + ((u_char *) (p))[0] = (u_char) ((n) >> 24); \ + ((u_char *) (p))[1] = (u_char) ((n) >> 16); \ + ((u_char *) (p))[2] = (u_char) ((n) >> 8); \ + ((u_char *) (p))[3] = (u_char) (n) + +#define ngx_mp4_get_64value(p) \ + ( ((uint64_t) ((u_char *) (p))[0] << 56) \ + + ((uint64_t) ((u_char *) (p))[1] << 48) \ + + ((uint64_t) ((u_char *) (p))[2] << 40) \ + + ((uint64_t) ((u_char *) (p))[3] << 32) \ + + ((uint64_t) ((u_char *) (p))[4] << 24) \ + + ( ((u_char *) (p))[5] << 16) \ + + ( ((u_char *) (p))[6] << 8) \ + + ( ((u_char *) (p))[7]) ) + +#define ngx_mp4_set_64value(p, n) \ + ((u_char *) (p))[0] = (u_char) ((uint64_t) (n) >> 56); \ + ((u_char *) (p))[1] = (u_char) ((uint64_t) (n) >> 48); \ + ((u_char *) (p))[2] = (u_char) ((uint64_t) (n) >> 40); \ + ((u_char *) (p))[3] = (u_char) ((uint64_t) (n) >> 32); \ + ((u_char *) (p))[4] = (u_char) ( (n) >> 24); \ + ((u_char *) (p))[5] = (u_char) ( (n) >> 16); \ + ((u_char *) (p))[6] = (u_char) ( (n) >> 8); \ + ((u_char *) (p))[7] = (u_char) (n) + +#define ngx_mp4_last_trak(mp4) \ + &((ngx_http_mp4_trak_t *) mp4->trak.elts)[mp4->trak.nelts - 1] + + +static ngx_int_t ngx_http_mp4_handler(ngx_http_request_t *r); + +static ngx_int_t ngx_http_mp4_process(ngx_http_mp4_file_t *mp4); +static ngx_int_t ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size); +static ngx_int_t ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, + off_t start_offset, off_t end_offset); +static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static void ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static ngx_int_t ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static void ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static ngx_int_t ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static void ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static ngx_int_t ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, ngx_uint_t start); +static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static void ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, ngx_uint_t start); +static ngx_int_t ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static void ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static void ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, ngx_uint_t start); +static ngx_int_t ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, ngx_uint_t start); +static ngx_int_t ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static ngx_int_t ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static void ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, int32_t adjustment); +static ngx_int_t ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4, + uint64_t atom_data_size); +static ngx_int_t ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak); +static void ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, off_t adjustment); + +static char *ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static void *ngx_http_mp4_create_conf(ngx_conf_t *cf); +static char *ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child); + + +static ngx_command_t ngx_http_mp4_commands[] = { + + { ngx_string("mp4"), + NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, + ngx_http_mp4, + 0, + 0, + NULL }, + + { ngx_string("mp4_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_mp4_conf_t, buffer_size), + NULL }, + + { ngx_string("mp4_max_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_mp4_conf_t, max_buffer_size), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_mp4_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + ngx_http_mp4_create_conf, /* create location configuration */ + ngx_http_mp4_merge_conf /* merge location configuration */ +}; + + +ngx_module_t ngx_http_mp4_module = { + NGX_MODULE_V1, + &ngx_http_mp4_module_ctx, /* module context */ + ngx_http_mp4_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_mp4_atom_handler_t ngx_http_mp4_atoms[] = { + { "ftyp", ngx_http_mp4_read_ftyp_atom }, + { "moov", ngx_http_mp4_read_moov_atom }, + { "mdat", ngx_http_mp4_read_mdat_atom }, + { NULL, NULL } +}; + +static ngx_http_mp4_atom_handler_t ngx_http_mp4_moov_atoms[] = { + { "mvhd", ngx_http_mp4_read_mvhd_atom }, + { "trak", ngx_http_mp4_read_trak_atom }, + { "cmov", ngx_http_mp4_read_cmov_atom }, + { NULL, NULL } +}; + +static ngx_http_mp4_atom_handler_t ngx_http_mp4_trak_atoms[] = { + { "tkhd", ngx_http_mp4_read_tkhd_atom }, + { "mdia", ngx_http_mp4_read_mdia_atom }, + { NULL, NULL } +}; + +static ngx_http_mp4_atom_handler_t ngx_http_mp4_mdia_atoms[] = { + { "mdhd", ngx_http_mp4_read_mdhd_atom }, + { "hdlr", ngx_http_mp4_read_hdlr_atom }, + { "minf", ngx_http_mp4_read_minf_atom }, + { NULL, NULL } +}; + +static ngx_http_mp4_atom_handler_t ngx_http_mp4_minf_atoms[] = { + { "vmhd", ngx_http_mp4_read_vmhd_atom }, + { "smhd", ngx_http_mp4_read_smhd_atom }, + { "dinf", ngx_http_mp4_read_dinf_atom }, + { "stbl", ngx_http_mp4_read_stbl_atom }, + { NULL, NULL } +}; + +static ngx_http_mp4_atom_handler_t ngx_http_mp4_stbl_atoms[] = { + { "stsd", ngx_http_mp4_read_stsd_atom }, + { "stts", ngx_http_mp4_read_stts_atom }, + { "stss", ngx_http_mp4_read_stss_atom }, + { "ctts", ngx_http_mp4_read_ctts_atom }, + { "stsc", ngx_http_mp4_read_stsc_atom }, + { "stsz", ngx_http_mp4_read_stsz_atom }, + { "stco", ngx_http_mp4_read_stco_atom }, + { "co64", ngx_http_mp4_read_co64_atom }, + { NULL, NULL } +}; + + +static ngx_int_t +ngx_http_mp4_handler(ngx_http_request_t *r) +{ + u_char *last; + size_t root; + ngx_int_t rc, start, end; + ngx_uint_t level, length; + ngx_str_t path, value; + ngx_log_t *log; + ngx_buf_t *b; + ngx_chain_t out; + ngx_http_mp4_file_t *mp4; + ngx_open_file_info_t of; + ngx_http_core_loc_conf_t *clcf; + + if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) { + return NGX_HTTP_NOT_ALLOWED; + } + + if (r->uri.data[r->uri.len - 1] == '/') { + return NGX_DECLINED; + } + + rc = ngx_http_discard_request_body(r); + + if (rc != NGX_OK) { + return rc; + } + + last = ngx_http_map_uri_to_path(r, &path, &root, 0); + if (last == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + log = r->connection->log; + + path.len = last - path.data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, + "http mp4 filename: \"%V\"", &path); + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + ngx_memzero(&of, sizeof(ngx_open_file_info_t)); + + of.read_ahead = clcf->read_ahead; + of.directio = NGX_MAX_OFF_T_VALUE; + of.valid = clcf->open_file_cache_valid; + of.min_uses = clcf->open_file_cache_min_uses; + of.errors = clcf->open_file_cache_errors; + of.events = clcf->open_file_cache_events; + + if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool) + != NGX_OK) + { + switch (of.err) { + + case 0: + return NGX_HTTP_INTERNAL_SERVER_ERROR; + + case NGX_ENOENT: + case NGX_ENOTDIR: + case NGX_ENAMETOOLONG: + + level = NGX_LOG_ERR; + rc = NGX_HTTP_NOT_FOUND; + break; + + case NGX_EACCES: +#if (NGX_HAVE_OPENAT) + case NGX_EMLINK: + case NGX_ELOOP: +#endif + + level = NGX_LOG_ERR; + rc = NGX_HTTP_FORBIDDEN; + break; + + default: + + level = NGX_LOG_CRIT; + rc = NGX_HTTP_INTERNAL_SERVER_ERROR; + break; + } + + if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) { + ngx_log_error(level, log, of.err, + "%s \"%s\" failed", of.failed, path.data); + } + + return rc; + } + + if (!of.is_file) { + + if (ngx_close_file(of.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_close_file_n " \"%s\" failed", path.data); + } + + return NGX_DECLINED; + } + + r->root_tested = !r->error_page; + r->allow_ranges = 1; + + start = -1; + length = 0; + r->headers_out.content_length_n = of.size; + mp4 = NULL; + b = NULL; + + if (r->args.len) { + + if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) { + + /* + * A Flash player may send start value with a lot of digits + * after dot so strtod() is used instead of atofp(). NaNs and + * infinities become negative numbers after (int) conversion. + */ + + ngx_set_errno(0); + start = (int) (strtod((char *) value.data, NULL) * 1000); + + if (ngx_errno != 0) { + start = -1; + } + } + + if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) { + + ngx_set_errno(0); + end = (int) (strtod((char *) value.data, NULL) * 1000); + + if (ngx_errno != 0) { + end = -1; + } + + if (end > 0) { + if (start < 0) { + start = 0; + } + + if (end > start) { + length = end - start; + } + } + } + } + + if (start >= 0) { + r->single_range = 1; + + mp4 = ngx_pcalloc(r->pool, sizeof(ngx_http_mp4_file_t)); + if (mp4 == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + mp4->file.fd = of.fd; + mp4->file.name = path; + mp4->file.log = r->connection->log; + mp4->end = of.size; + mp4->start = (ngx_uint_t) start; + mp4->length = length; + mp4->request = r; + + switch (ngx_http_mp4_process(mp4)) { + + case NGX_DECLINED: + if (mp4->buffer) { + ngx_pfree(r->pool, mp4->buffer); + } + + ngx_pfree(r->pool, mp4); + mp4 = NULL; + + break; + + case NGX_OK: + r->headers_out.content_length_n = mp4->content_length; + break; + + default: /* NGX_ERROR */ + if (mp4->buffer) { + ngx_pfree(r->pool, mp4->buffer); + } + + ngx_pfree(r->pool, mp4); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } + + log->action = "sending mp4 to client"; + + if (clcf->directio <= of.size) { + + /* + * DIRECTIO is set on transfer only + * to allow kernel to cache "moov" atom + */ + + if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + ngx_directio_on_n " \"%s\" failed", path.data); + } + + of.is_directio = 1; + + if (mp4) { + mp4->file.directio = 1; + } + } + + r->headers_out.status = NGX_HTTP_OK; + r->headers_out.last_modified_time = of.mtime; + + if (ngx_http_set_etag(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (ngx_http_set_content_type(r) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + if (mp4 == NULL) { + b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t)); + if (b->file == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + } + + rc = ngx_http_send_header(r); + + if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { + return rc; + } + + if (mp4) { + return ngx_http_output_filter(r, mp4->out); + } + + b->file_pos = 0; + b->file_last = of.size; + + b->in_file = b->file_last ? 1 : 0; + b->last_buf = (r == r->main) ? 1 : 0; + b->last_in_chain = 1; + + b->file->fd = of.fd; + b->file->name = path; + b->file->log = log; + b->file->directio = of.is_directio; + + out.buf = b; + out.next = NULL; + + return ngx_http_output_filter(r, &out); +} + + +static ngx_int_t +ngx_http_mp4_process(ngx_http_mp4_file_t *mp4) +{ + off_t start_offset, end_offset, adjustment; + ngx_int_t rc; + ngx_uint_t i, j; + ngx_chain_t **prev; + ngx_http_mp4_trak_t *trak; + ngx_http_mp4_conf_t *conf; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 start:%ui, length:%ui", mp4->start, mp4->length); + + conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module); + + mp4->buffer_size = conf->buffer_size; + + rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_atoms, mp4->end); + if (rc != NGX_OK) { + return rc; + } + + if (mp4->trak.nelts == 0) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "no mp4 trak atoms were found in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + if (mp4->mdat_atom.buf == NULL) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "no mp4 mdat atom was found in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + prev = &mp4->out; + + if (mp4->ftyp_atom.buf) { + *prev = &mp4->ftyp_atom; + prev = &mp4->ftyp_atom.next; + } + + *prev = &mp4->moov_atom; + prev = &mp4->moov_atom.next; + + if (mp4->mvhd_atom.buf) { + mp4->moov_size += mp4->mvhd_atom_buf.last - mp4->mvhd_atom_buf.pos; + *prev = &mp4->mvhd_atom; + prev = &mp4->mvhd_atom.next; + } + + start_offset = mp4->end; + end_offset = 0; + trak = mp4->trak.elts; + + for (i = 0; i < mp4->trak.nelts; i++) { + + if (ngx_http_mp4_update_stts_atom(mp4, &trak[i]) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_mp4_update_stss_atom(mp4, &trak[i]) != NGX_OK) { + return NGX_ERROR; + } + + ngx_http_mp4_update_ctts_atom(mp4, &trak[i]); + + if (ngx_http_mp4_update_stsc_atom(mp4, &trak[i]) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_mp4_update_stsz_atom(mp4, &trak[i]) != NGX_OK) { + return NGX_ERROR; + } + + if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) { + if (ngx_http_mp4_update_co64_atom(mp4, &trak[i]) != NGX_OK) { + return NGX_ERROR; + } + + } else { + if (ngx_http_mp4_update_stco_atom(mp4, &trak[i]) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_http_mp4_update_stbl_atom(mp4, &trak[i]); + ngx_http_mp4_update_minf_atom(mp4, &trak[i]); + trak[i].size += trak[i].mdhd_size; + trak[i].size += trak[i].hdlr_size; + ngx_http_mp4_update_mdia_atom(mp4, &trak[i]); + trak[i].size += trak[i].tkhd_size; + ngx_http_mp4_update_trak_atom(mp4, &trak[i]); + + mp4->moov_size += trak[i].size; + + if (start_offset > trak[i].start_offset) { + start_offset = trak[i].start_offset; + } + + if (end_offset < trak[i].end_offset) { + end_offset = trak[i].end_offset; + } + + *prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM]; + prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM].next; + + for (j = 0; j < NGX_HTTP_MP4_LAST_ATOM + 1; j++) { + if (trak[i].out[j].buf) { + *prev = &trak[i].out[j]; + prev = &trak[i].out[j].next; + } + } + } + + if (end_offset < start_offset) { + end_offset = start_offset; + } + + mp4->moov_size += 8; + + ngx_mp4_set_32value(mp4->moov_atom_header, mp4->moov_size); + ngx_mp4_set_atom_name(mp4->moov_atom_header, 'm', 'o', 'o', 'v'); + mp4->content_length += mp4->moov_size; + + *prev = &mp4->mdat_atom; + + if (start_offset > mp4->mdat_data.buf->file_last) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "start time is out mp4 mdat atom in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + adjustment = mp4->ftyp_size + mp4->moov_size + + ngx_http_mp4_update_mdat_atom(mp4, start_offset, end_offset) + - start_offset; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 adjustment:%O", adjustment); + + for (i = 0; i < mp4->trak.nelts; i++) { + if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) { + ngx_http_mp4_adjust_co64_atom(mp4, &trak[i], adjustment); + } else { + ngx_http_mp4_adjust_stco_atom(mp4, &trak[i], (int32_t) adjustment); + } + } + + return NGX_OK; +} + + +typedef struct { + u_char size[4]; + u_char name[4]; +} ngx_mp4_atom_header_t; + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char size64[8]; +} ngx_mp4_atom_header64_t; + + +static ngx_int_t +ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size) +{ + off_t end; + size_t atom_header_size; + u_char *atom_header, *atom_name; + uint64_t atom_size; + ngx_int_t rc; + ngx_uint_t n; + + end = mp4->offset + atom_data_size; + + while (mp4->offset < end) { + + if (ngx_http_mp4_read(mp4, sizeof(uint32_t)) != NGX_OK) { + return NGX_ERROR; + } + + atom_header = mp4->buffer_pos; + atom_size = ngx_mp4_get_32value(atom_header); + atom_header_size = sizeof(ngx_mp4_atom_header_t); + + if (atom_size == 0) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 atom end"); + return NGX_OK; + } + + if (atom_size < sizeof(ngx_mp4_atom_header_t)) { + + if (atom_size == 1) { + + if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header64_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + /* 64-bit atom size */ + atom_header = mp4->buffer_pos; + atom_size = ngx_mp4_get_64value(atom_header + 8); + atom_header_size = sizeof(ngx_mp4_atom_header64_t); + + } else { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 atom is too small:%uL", + mp4->file.name.data, atom_size); + return NGX_ERROR; + } + } + + if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header_t)) != NGX_OK) { + return NGX_ERROR; + } + + atom_header = mp4->buffer_pos; + atom_name = atom_header + sizeof(uint32_t); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 atom: %*s @%O:%uL", + 4, atom_name, mp4->offset, atom_size); + + if (atom_size > (uint64_t) (NGX_MAX_OFF_T_VALUE - mp4->offset) + || mp4->offset + (off_t) atom_size > end) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 atom too large:%uL", + mp4->file.name.data, atom_size); + return NGX_ERROR; + } + + for (n = 0; atom[n].name; n++) { + + if (ngx_strncmp(atom_name, atom[n].name, 4) == 0) { + + ngx_mp4_atom_next(mp4, atom_header_size); + + rc = atom[n].handler(mp4, atom_size - atom_header_size); + if (rc != NGX_OK) { + return rc; + } + + goto next; + } + } + + ngx_mp4_atom_next(mp4, atom_size); + + next: + continue; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size) +{ + ssize_t n; + + if (mp4->buffer_pos + size <= mp4->buffer_end) { + return NGX_OK; + } + + if (mp4->offset + (off_t) mp4->buffer_size > mp4->end) { + mp4->buffer_size = (size_t) (mp4->end - mp4->offset); + } + + if (mp4->buffer_size < size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 file truncated", mp4->file.name.data); + return NGX_ERROR; + } + + if (mp4->buffer == NULL) { + mp4->buffer = ngx_palloc(mp4->request->pool, mp4->buffer_size); + if (mp4->buffer == NULL) { + return NGX_ERROR; + } + + mp4->buffer_start = mp4->buffer; + } + + n = ngx_read_file(&mp4->file, mp4->buffer_start, mp4->buffer_size, + mp4->offset); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if ((size_t) n != mp4->buffer_size) { + ngx_log_error(NGX_LOG_CRIT, mp4->file.log, 0, + ngx_read_file_n " read only %z of %z from \"%s\"", + n, mp4->buffer_size, mp4->file.name.data); + return NGX_ERROR; + } + + mp4->buffer_pos = mp4->buffer_start; + mp4->buffer_end = mp4->buffer_start + mp4->buffer_size; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *ftyp_atom; + size_t atom_size; + ngx_buf_t *atom; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ftyp atom"); + + if (atom_data_size > 1024 + || ngx_mp4_atom_data(mp4) + (size_t) atom_data_size > mp4->buffer_end) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 ftyp atom is too large:%uL", + mp4->file.name.data, atom_data_size); + return NGX_ERROR; + } + + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + + ftyp_atom = ngx_palloc(mp4->request->pool, atom_size); + if (ftyp_atom == NULL) { + return NGX_ERROR; + } + + ngx_mp4_set_32value(ftyp_atom, atom_size); + ngx_mp4_set_atom_name(ftyp_atom, 'f', 't', 'y', 'p'); + + /* + * only moov atom content is guaranteed to be in mp4->buffer + * during sending response, so ftyp atom content should be copied + */ + ngx_memcpy(ftyp_atom + sizeof(ngx_mp4_atom_header_t), + ngx_mp4_atom_data(mp4), (size_t) atom_data_size); + + atom = &mp4->ftyp_atom_buf; + atom->temporary = 1; + atom->pos = ftyp_atom; + atom->last = ftyp_atom + atom_size; + + mp4->ftyp_atom.buf = atom; + mp4->ftyp_size = atom_size; + mp4->content_length = atom_size; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +/* + * Small excess buffer to process atoms after moov atom, mp4->buffer_start + * will be set to this buffer part after moov atom processing. + */ +#define NGX_HTTP_MP4_MOOV_BUFFER_EXCESS (4 * 1024) + +static ngx_int_t +ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + ngx_int_t rc; + ngx_uint_t no_mdat; + ngx_buf_t *atom; + ngx_http_mp4_conf_t *conf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom"); + + no_mdat = (mp4->mdat_atom.buf == NULL); + + if (no_mdat && mp4->start == 0 && mp4->length == 0) { + /* + * send original file if moov atom resides before + * mdat atom and client requests integral file + */ + return NGX_DECLINED; + } + + conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module); + + if (atom_data_size > mp4->buffer_size) { + + if (atom_data_size > conf->max_buffer_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 moov atom is too large:%uL, " + "you may want to increase mp4_max_buffer_size", + mp4->file.name.data, atom_data_size); + return NGX_ERROR; + } + + ngx_pfree(mp4->request->pool, mp4->buffer); + mp4->buffer = NULL; + mp4->buffer_pos = NULL; + mp4->buffer_end = NULL; + + mp4->buffer_size = (size_t) atom_data_size + + NGX_HTTP_MP4_MOOV_BUFFER_EXCESS * no_mdat; + } + + if (ngx_http_mp4_read(mp4, (size_t) atom_data_size) != NGX_OK) { + return NGX_ERROR; + } + + mp4->trak.elts = &mp4->traks; + mp4->trak.size = sizeof(ngx_http_mp4_trak_t); + mp4->trak.nalloc = 2; + mp4->trak.pool = mp4->request->pool; + + atom = &mp4->moov_atom_buf; + atom->temporary = 1; + atom->pos = mp4->moov_atom_header; + atom->last = mp4->moov_atom_header + 8; + + mp4->moov_atom.buf = &mp4->moov_atom_buf; + + rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_moov_atoms, atom_data_size); + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom done"); + + if (no_mdat) { + mp4->buffer_start = mp4->buffer_pos; + mp4->buffer_size = NGX_HTTP_MP4_MOOV_BUFFER_EXCESS; + + if (mp4->buffer_start + mp4->buffer_size > mp4->buffer_end) { + mp4->buffer = NULL; + mp4->buffer_pos = NULL; + mp4->buffer_end = NULL; + } + + } else { + /* skip atoms after moov atom */ + mp4->offset = mp4->end; + } + + return rc; +} + + +static ngx_int_t +ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + ngx_buf_t *data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdat atom"); + + data = &mp4->mdat_data_buf; + data->file = &mp4->file; + data->in_file = 1; + data->last_buf = 1; + data->last_in_chain = 1; + data->file_last = mp4->offset + atom_data_size; + + mp4->mdat_atom.buf = &mp4->mdat_atom_buf; + mp4->mdat_atom.next = &mp4->mdat_data; + mp4->mdat_data.buf = data; + + if (mp4->trak.nelts) { + /* skip atoms after mdat atom */ + mp4->offset = mp4->end; + + } else { + ngx_mp4_atom_next(mp4, atom_data_size); + } + + return NGX_OK; +} + + +static size_t +ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset, + off_t end_offset) +{ + off_t atom_data_size; + u_char *atom_header; + uint32_t atom_header_size; + uint64_t atom_size; + ngx_buf_t *atom; + + atom_data_size = end_offset - start_offset; + mp4->mdat_data.buf->file_pos = start_offset; + mp4->mdat_data.buf->file_last = end_offset; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mdat new offset @%O:%O", start_offset, atom_data_size); + + atom_header = mp4->mdat_atom_header; + + if ((uint64_t) atom_data_size > (uint64_t) 0xffffffff) { + atom_size = 1; + atom_header_size = sizeof(ngx_mp4_atom_header64_t); + ngx_mp4_set_64value(atom_header + sizeof(ngx_mp4_atom_header_t), + sizeof(ngx_mp4_atom_header64_t) + atom_data_size); + } else { + atom_size = sizeof(ngx_mp4_atom_header_t) + atom_data_size; + atom_header_size = sizeof(ngx_mp4_atom_header_t); + } + + mp4->content_length += atom_header_size + atom_data_size; + + ngx_mp4_set_32value(atom_header, atom_size); + ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'a', 't'); + + atom = &mp4->mdat_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + atom_header_size; + + return atom_header_size; +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char creation_time[4]; + u_char modification_time[4]; + u_char timescale[4]; + u_char duration[4]; + u_char rate[4]; + u_char volume[2]; + u_char reserved[10]; + u_char matrix[36]; + u_char preview_time[4]; + u_char preview_duration[4]; + u_char poster_time[4]; + u_char selection_time[4]; + u_char selection_duration[4]; + u_char current_time[4]; + u_char next_track_id[4]; +} ngx_mp4_mvhd_atom_t; + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char creation_time[8]; + u_char modification_time[8]; + u_char timescale[4]; + u_char duration[8]; + u_char rate[4]; + u_char volume[2]; + u_char reserved[10]; + u_char matrix[36]; + u_char preview_time[4]; + u_char preview_duration[4]; + u_char poster_time[4]; + u_char selection_time[4]; + u_char selection_duration[4]; + u_char current_time[4]; + u_char next_track_id[4]; +} ngx_mp4_mvhd64_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + size_t atom_size; + uint32_t timescale; + uint64_t duration, start_time, length_time; + ngx_buf_t *atom; + ngx_mp4_mvhd_atom_t *mvhd_atom; + ngx_mp4_mvhd64_atom_t *mvhd64_atom; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mvhd atom"); + + atom_header = ngx_mp4_atom_header(mp4); + mvhd_atom = (ngx_mp4_mvhd_atom_t *) atom_header; + mvhd64_atom = (ngx_mp4_mvhd64_atom_t *) atom_header; + ngx_mp4_set_atom_name(atom_header, 'm', 'v', 'h', 'd'); + + if (ngx_mp4_atom_data_size(ngx_mp4_mvhd_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 mvhd atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + if (mvhd_atom->version[0] == 0) { + /* version 0: 32-bit duration */ + timescale = ngx_mp4_get_32value(mvhd_atom->timescale); + duration = ngx_mp4_get_32value(mvhd_atom->duration); + + } else { + /* version 1: 64-bit duration */ + + if (ngx_mp4_atom_data_size(ngx_mp4_mvhd64_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 mvhd atom too small", + mp4->file.name.data); + return NGX_ERROR; + } + + timescale = ngx_mp4_get_32value(mvhd64_atom->timescale); + duration = ngx_mp4_get_64value(mvhd64_atom->duration); + } + + mp4->timescale = timescale; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mvhd timescale:%uD, duration:%uL, time:%.3fs", + timescale, duration, (double) duration / timescale); + + start_time = (uint64_t) mp4->start * timescale / 1000; + + if (duration < start_time) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 start time exceeds file duration", + mp4->file.name.data); + return NGX_ERROR; + } + + duration -= start_time; + + if (mp4->length) { + length_time = (uint64_t) mp4->length * timescale / 1000; + + if (duration > length_time) { + duration = length_time; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mvhd new duration:%uL, time:%.3fs", + duration, (double) duration / timescale); + + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + ngx_mp4_set_32value(mvhd_atom->size, atom_size); + + if (mvhd_atom->version[0] == 0) { + ngx_mp4_set_32value(mvhd_atom->duration, duration); + + } else { + ngx_mp4_set_64value(mvhd64_atom->duration, duration); + } + + atom = &mp4->mvhd_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + atom_size; + + mp4->mvhd_atom.buf = atom; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_end; + off_t atom_file_end; + ngx_int_t rc; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 trak atom"); + + trak = ngx_array_push(&mp4->trak); + if (trak == NULL) { + return NGX_ERROR; + } + + ngx_memzero(trak, sizeof(ngx_http_mp4_trak_t)); + + atom_header = ngx_mp4_atom_header(mp4); + ngx_mp4_set_atom_name(atom_header, 't', 'r', 'a', 'k'); + + atom = &trak->trak_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + sizeof(ngx_mp4_atom_header_t); + + trak->out[NGX_HTTP_MP4_TRAK_ATOM].buf = atom; + + atom_end = mp4->buffer_pos + (size_t) atom_data_size; + atom_file_end = mp4->offset + atom_data_size; + + rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_trak_atoms, atom_data_size); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 trak atom: %i", rc); + + if (rc == NGX_DECLINED) { + /* skip this trak */ + ngx_memzero(trak, sizeof(ngx_http_mp4_trak_t)); + mp4->trak.nelts--; + mp4->buffer_pos = atom_end; + mp4->offset = atom_file_end; + return NGX_OK; + } + + return rc; +} + + +static void +ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + ngx_buf_t *atom; + + trak->size += sizeof(ngx_mp4_atom_header_t); + atom = &trak->trak_atom_buf; + ngx_mp4_set_32value(atom->pos, trak->size); +} + + +static ngx_int_t +ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 compressed moov atom (cmov) is not supported", + mp4->file.name.data); + + return NGX_ERROR; +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char creation_time[4]; + u_char modification_time[4]; + u_char track_id[4]; + u_char reserved1[4]; + u_char duration[4]; + u_char reserved2[8]; + u_char layer[2]; + u_char group[2]; + u_char volume[2]; + u_char reverved3[2]; + u_char matrix[36]; + u_char width[4]; + u_char heigth[4]; +} ngx_mp4_tkhd_atom_t; + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char creation_time[8]; + u_char modification_time[8]; + u_char track_id[4]; + u_char reserved1[4]; + u_char duration[8]; + u_char reserved2[8]; + u_char layer[2]; + u_char group[2]; + u_char volume[2]; + u_char reverved3[2]; + u_char matrix[36]; + u_char width[4]; + u_char heigth[4]; +} ngx_mp4_tkhd64_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + size_t atom_size; + uint64_t duration, start_time, length_time; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + ngx_mp4_tkhd_atom_t *tkhd_atom; + ngx_mp4_tkhd64_atom_t *tkhd64_atom; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 tkhd atom"); + + atom_header = ngx_mp4_atom_header(mp4); + tkhd_atom = (ngx_mp4_tkhd_atom_t *) atom_header; + tkhd64_atom = (ngx_mp4_tkhd64_atom_t *) atom_header; + ngx_mp4_set_atom_name(tkhd_atom, 't', 'k', 'h', 'd'); + + if (ngx_mp4_atom_data_size(ngx_mp4_tkhd_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 tkhd atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + if (tkhd_atom->version[0] == 0) { + /* version 0: 32-bit duration */ + duration = ngx_mp4_get_32value(tkhd_atom->duration); + + } else { + /* version 1: 64-bit duration */ + + if (ngx_mp4_atom_data_size(ngx_mp4_tkhd64_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 tkhd atom too small", + mp4->file.name.data); + return NGX_ERROR; + } + + duration = ngx_mp4_get_64value(tkhd64_atom->duration); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "tkhd duration:%uL, time:%.3fs", + duration, (double) duration / mp4->timescale); + + start_time = (uint64_t) mp4->start * mp4->timescale / 1000; + + if (duration <= start_time) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "tkhd duration is less than start time"); + return NGX_DECLINED; + } + + duration -= start_time; + + if (mp4->length) { + length_time = (uint64_t) mp4->length * mp4->timescale / 1000; + + if (duration > length_time) { + duration = length_time; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "tkhd new duration:%uL, time:%.3fs", + duration, (double) duration / mp4->timescale); + + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + + trak = ngx_mp4_last_trak(mp4); + trak->tkhd_size = atom_size; + + ngx_mp4_set_32value(tkhd_atom->size, atom_size); + + if (tkhd_atom->version[0] == 0) { + ngx_mp4_set_32value(tkhd_atom->duration, duration); + + } else { + ngx_mp4_set_64value(tkhd64_atom->duration, duration); + } + + atom = &trak->tkhd_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + atom_size; + + trak->out[NGX_HTTP_MP4_TKHD_ATOM].buf = atom; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process mdia atom"); + + atom_header = ngx_mp4_atom_header(mp4); + ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'i', 'a'); + + trak = ngx_mp4_last_trak(mp4); + + atom = &trak->mdia_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + sizeof(ngx_mp4_atom_header_t); + + trak->out[NGX_HTTP_MP4_MDIA_ATOM].buf = atom; + + return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_mdia_atoms, atom_data_size); +} + + +static void +ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + ngx_buf_t *atom; + + trak->size += sizeof(ngx_mp4_atom_header_t); + atom = &trak->mdia_atom_buf; + ngx_mp4_set_32value(atom->pos, trak->size); +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char creation_time[4]; + u_char modification_time[4]; + u_char timescale[4]; + u_char duration[4]; + u_char language[2]; + u_char quality[2]; +} ngx_mp4_mdhd_atom_t; + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char creation_time[8]; + u_char modification_time[8]; + u_char timescale[4]; + u_char duration[8]; + u_char language[2]; + u_char quality[2]; +} ngx_mp4_mdhd64_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + size_t atom_size; + uint32_t timescale; + uint64_t duration, start_time, length_time; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + ngx_mp4_mdhd_atom_t *mdhd_atom; + ngx_mp4_mdhd64_atom_t *mdhd64_atom; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdhd atom"); + + atom_header = ngx_mp4_atom_header(mp4); + mdhd_atom = (ngx_mp4_mdhd_atom_t *) atom_header; + mdhd64_atom = (ngx_mp4_mdhd64_atom_t *) atom_header; + ngx_mp4_set_atom_name(mdhd_atom, 'm', 'd', 'h', 'd'); + + if (ngx_mp4_atom_data_size(ngx_mp4_mdhd_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 mdhd atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + if (mdhd_atom->version[0] == 0) { + /* version 0: everything is 32-bit */ + timescale = ngx_mp4_get_32value(mdhd_atom->timescale); + duration = ngx_mp4_get_32value(mdhd_atom->duration); + + } else { + /* version 1: 64-bit duration and 32-bit timescale */ + + if (ngx_mp4_atom_data_size(ngx_mp4_mdhd64_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 mdhd atom too small", + mp4->file.name.data); + return NGX_ERROR; + } + + timescale = ngx_mp4_get_32value(mdhd64_atom->timescale); + duration = ngx_mp4_get_64value(mdhd64_atom->duration); + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mdhd timescale:%uD, duration:%uL, time:%.3fs", + timescale, duration, (double) duration / timescale); + + start_time = (uint64_t) mp4->start * timescale / 1000; + + if (duration <= start_time) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mdhd duration is less than start time"); + return NGX_DECLINED; + } + + duration -= start_time; + + if (mp4->length) { + length_time = (uint64_t) mp4->length * timescale / 1000; + + if (duration > length_time) { + duration = length_time; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mdhd new duration:%uL, time:%.3fs", + duration, (double) duration / timescale); + + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + + trak = ngx_mp4_last_trak(mp4); + trak->mdhd_size = atom_size; + trak->timescale = timescale; + + ngx_mp4_set_32value(mdhd_atom->size, atom_size); + + if (mdhd_atom->version[0] == 0) { + ngx_mp4_set_32value(mdhd_atom->duration, duration); + + } else { + ngx_mp4_set_64value(mdhd64_atom->duration, duration); + } + + atom = &trak->mdhd_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + atom_size; + + trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf = atom; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + size_t atom_size; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 hdlr atom"); + + atom_header = ngx_mp4_atom_header(mp4); + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + ngx_mp4_set_32value(atom_header, atom_size); + ngx_mp4_set_atom_name(atom_header, 'h', 'd', 'l', 'r'); + + trak = ngx_mp4_last_trak(mp4); + + atom = &trak->hdlr_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + atom_size; + + trak->hdlr_size = atom_size; + trak->out[NGX_HTTP_MP4_HDLR_ATOM].buf = atom; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process minf atom"); + + atom_header = ngx_mp4_atom_header(mp4); + ngx_mp4_set_atom_name(atom_header, 'm', 'i', 'n', 'f'); + + trak = ngx_mp4_last_trak(mp4); + + atom = &trak->minf_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + sizeof(ngx_mp4_atom_header_t); + + trak->out[NGX_HTTP_MP4_MINF_ATOM].buf = atom; + + return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_minf_atoms, atom_data_size); +} + + +static void +ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + ngx_buf_t *atom; + + trak->size += sizeof(ngx_mp4_atom_header_t) + + trak->vmhd_size + + trak->smhd_size + + trak->dinf_size; + atom = &trak->minf_atom_buf; + ngx_mp4_set_32value(atom->pos, trak->size); +} + + +static ngx_int_t +ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + size_t atom_size; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 vmhd atom"); + + atom_header = ngx_mp4_atom_header(mp4); + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + ngx_mp4_set_32value(atom_header, atom_size); + ngx_mp4_set_atom_name(atom_header, 'v', 'm', 'h', 'd'); + + trak = ngx_mp4_last_trak(mp4); + + atom = &trak->vmhd_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + atom_size; + + trak->vmhd_size += atom_size; + trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf = atom; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + size_t atom_size; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 smhd atom"); + + atom_header = ngx_mp4_atom_header(mp4); + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + ngx_mp4_set_32value(atom_header, atom_size); + ngx_mp4_set_atom_name(atom_header, 's', 'm', 'h', 'd'); + + trak = ngx_mp4_last_trak(mp4); + + atom = &trak->smhd_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + atom_size; + + trak->smhd_size += atom_size; + trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf = atom; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + size_t atom_size; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 dinf atom"); + + atom_header = ngx_mp4_atom_header(mp4); + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + ngx_mp4_set_32value(atom_header, atom_size); + ngx_mp4_set_atom_name(atom_header, 'd', 'i', 'n', 'f'); + + trak = ngx_mp4_last_trak(mp4); + + atom = &trak->dinf_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + atom_size; + + trak->dinf_size += atom_size; + trak->out[NGX_HTTP_MP4_DINF_ATOM].buf = atom; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header; + ngx_buf_t *atom; + ngx_http_mp4_trak_t *trak; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process stbl atom"); + + atom_header = ngx_mp4_atom_header(mp4); + ngx_mp4_set_atom_name(atom_header, 's', 't', 'b', 'l'); + + trak = ngx_mp4_last_trak(mp4); + + atom = &trak->stbl_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_header + sizeof(ngx_mp4_atom_header_t); + + trak->out[NGX_HTTP_MP4_STBL_ATOM].buf = atom; + + return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_stbl_atoms, atom_data_size); +} + + +static void +ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + ngx_buf_t *atom; + + trak->size += sizeof(ngx_mp4_atom_header_t); + atom = &trak->stbl_atom_buf; + ngx_mp4_set_32value(atom->pos, trak->size); +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char entries[4]; + + u_char media_size[4]; + u_char media_name[4]; +} ngx_mp4_stsd_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_table; + size_t atom_size; + ngx_buf_t *atom; + ngx_mp4_stsd_atom_t *stsd_atom; + ngx_http_mp4_trak_t *trak; + + /* sample description atom */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsd atom"); + + atom_header = ngx_mp4_atom_header(mp4); + stsd_atom = (ngx_mp4_stsd_atom_t *) atom_header; + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + atom_table = atom_header + atom_size; + ngx_mp4_set_32value(stsd_atom->size, atom_size); + ngx_mp4_set_atom_name(stsd_atom, 's', 't', 's', 'd'); + + if (ngx_mp4_atom_data_size(ngx_mp4_stsd_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stsd atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "stsd entries:%uD, media:%*s", + ngx_mp4_get_32value(stsd_atom->entries), + 4, stsd_atom->media_name); + + trak = ngx_mp4_last_trak(mp4); + + atom = &trak->stsd_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_table; + + trak->out[NGX_HTTP_MP4_STSD_ATOM].buf = atom; + trak->size += atom_size; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char entries[4]; +} ngx_mp4_stts_atom_t; + +typedef struct { + u_char count[4]; + u_char duration[4]; +} ngx_mp4_stts_entry_t; + + +static ngx_int_t +ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_table, *atom_end; + uint32_t entries; + ngx_buf_t *atom, *data; + ngx_mp4_stts_atom_t *stts_atom; + ngx_http_mp4_trak_t *trak; + + /* time-to-sample atom */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stts atom"); + + atom_header = ngx_mp4_atom_header(mp4); + stts_atom = (ngx_mp4_stts_atom_t *) atom_header; + ngx_mp4_set_atom_name(stts_atom, 's', 't', 't', 's'); + + if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stts atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + entries = ngx_mp4_get_32value(stts_atom->entries); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 time-to-sample entries:%uD", entries); + + if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) + + entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stts atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + atom_table = atom_header + sizeof(ngx_mp4_stts_atom_t); + atom_end = atom_table + entries * sizeof(ngx_mp4_stts_entry_t); + + trak = ngx_mp4_last_trak(mp4); + trak->time_to_sample_entries = entries; + + atom = &trak->stts_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_table; + + data = &trak->stts_data_buf; + data->temporary = 1; + data->pos = atom_table; + data->last = atom_end; + + trak->out[NGX_HTTP_MP4_STTS_ATOM].buf = atom; + trak->out[NGX_HTTP_MP4_STTS_DATA].buf = data; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + size_t atom_size; + ngx_buf_t *atom, *data; + ngx_mp4_stts_atom_t *stts_atom; + + /* + * mdia.minf.stbl.stts updating requires trak->timescale + * from mdia.mdhd atom which may reside after mdia.minf + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stts atom update"); + + data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf; + + if (data == NULL) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "no mp4 stts atoms were found in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + if (ngx_http_mp4_crop_stts_data(mp4, trak, 1) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_mp4_crop_stts_data(mp4, trak, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "time-to-sample entries:%uD", trak->time_to_sample_entries); + + atom_size = sizeof(ngx_mp4_stts_atom_t) + (data->last - data->pos); + trak->size += atom_size; + + atom = trak->out[NGX_HTTP_MP4_STTS_ATOM].buf; + stts_atom = (ngx_mp4_stts_atom_t *) atom->pos; + ngx_mp4_set_32value(stts_atom->size, atom_size); + ngx_mp4_set_32value(stts_atom->entries, trak->time_to_sample_entries); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, ngx_uint_t start) +{ + uint32_t count, duration, rest; + uint64_t start_time; + ngx_buf_t *data; + ngx_uint_t start_sample, entries, start_sec; + ngx_mp4_stts_entry_t *entry, *end; + + if (start) { + start_sec = mp4->start; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stts crop start_time:%ui", start_sec); + + } else if (mp4->length) { + start_sec = mp4->length; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stts crop end_time:%ui", start_sec); + + } else { + return NGX_OK; + } + + data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf; + + start_time = (uint64_t) start_sec * trak->timescale / 1000; + + entries = trak->time_to_sample_entries; + start_sample = 0; + entry = (ngx_mp4_stts_entry_t *) data->pos; + end = (ngx_mp4_stts_entry_t *) data->last; + + while (entry < end) { + count = ngx_mp4_get_32value(entry->count); + duration = ngx_mp4_get_32value(entry->duration); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "time:%uL, count:%uD, duration:%uD", + start_time, count, duration); + + if (start_time < (uint64_t) count * duration) { + start_sample += (ngx_uint_t) (start_time / duration); + rest = (uint32_t) (start_time / duration); + goto found; + } + + start_sample += count; + start_time -= count * duration; + entries--; + entry++; + } + + if (start) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "start time is out mp4 stts samples in \"%s\"", + mp4->file.name.data); + + return NGX_ERROR; + + } else { + trak->end_sample = trak->start_sample + start_sample; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "end_sample:%ui", trak->end_sample); + + return NGX_OK; + } + +found: + + if (start) { + ngx_mp4_set_32value(entry->count, count - rest); + data->pos = (u_char *) entry; + trak->time_to_sample_entries = entries; + trak->start_sample = start_sample; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "start_sample:%ui, new count:%uD", + trak->start_sample, count - rest); + + } else { + ngx_mp4_set_32value(entry->count, rest); + data->last = (u_char *) (entry + 1); + trak->time_to_sample_entries -= entries - 1; + trak->end_sample = trak->start_sample + start_sample; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "end_sample:%ui, new count:%uD", + trak->end_sample, rest); + } + + return NGX_OK; +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char entries[4]; +} ngx_http_mp4_stss_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_table, *atom_end; + uint32_t entries; + ngx_buf_t *atom, *data; + ngx_http_mp4_trak_t *trak; + ngx_http_mp4_stss_atom_t *stss_atom; + + /* sync samples atom */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stss atom"); + + atom_header = ngx_mp4_atom_header(mp4); + stss_atom = (ngx_http_mp4_stss_atom_t *) atom_header; + ngx_mp4_set_atom_name(stss_atom, 's', 't', 's', 's'); + + if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stss atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + entries = ngx_mp4_get_32value(stss_atom->entries); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sync sample entries:%uD", entries); + + trak = ngx_mp4_last_trak(mp4); + trak->sync_samples_entries = entries; + + atom_table = atom_header + sizeof(ngx_http_mp4_stss_atom_t); + + atom = &trak->stss_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_table; + + if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) + + entries * sizeof(uint32_t) > atom_data_size) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stss atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + atom_end = atom_table + entries * sizeof(uint32_t); + + data = &trak->stss_data_buf; + data->temporary = 1; + data->pos = atom_table; + data->last = atom_end; + + trak->out[NGX_HTTP_MP4_STSS_ATOM].buf = atom; + trak->out[NGX_HTTP_MP4_STSS_DATA].buf = data; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + size_t atom_size; + uint32_t sample, start_sample, *entry, *end; + ngx_buf_t *atom, *data; + ngx_http_mp4_stss_atom_t *stss_atom; + + /* + * mdia.minf.stbl.stss updating requires trak->start_sample + * from mdia.minf.stbl.stts which depends on value from mdia.mdhd + * atom which may reside after mdia.minf + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stss atom update"); + + data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf; + + if (data == NULL) { + return NGX_OK; + } + + ngx_http_mp4_crop_stss_data(mp4, trak, 1); + ngx_http_mp4_crop_stss_data(mp4, trak, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sync sample entries:%uD", trak->sync_samples_entries); + + if (trak->sync_samples_entries) { + entry = (uint32_t *) data->pos; + end = (uint32_t *) data->last; + + start_sample = trak->start_sample; + + while (entry < end) { + sample = ngx_mp4_get_32value(entry); + sample -= start_sample; + ngx_mp4_set_32value(entry, sample); + entry++; + } + + } else { + trak->out[NGX_HTTP_MP4_STSS_DATA].buf = NULL; + } + + atom_size = sizeof(ngx_http_mp4_stss_atom_t) + (data->last - data->pos); + trak->size += atom_size; + + atom = trak->out[NGX_HTTP_MP4_STSS_ATOM].buf; + stss_atom = (ngx_http_mp4_stss_atom_t *) atom->pos; + + ngx_mp4_set_32value(stss_atom->size, atom_size); + ngx_mp4_set_32value(stss_atom->entries, trak->sync_samples_entries); + + return NGX_OK; +} + + +static void +ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, ngx_uint_t start) +{ + uint32_t sample, start_sample, *entry, *end; + ngx_buf_t *data; + ngx_uint_t entries; + + /* sync samples starts from 1 */ + + if (start) { + start_sample = trak->start_sample + 1; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stss crop start_sample:%uD", start_sample); + + } else if (mp4->length) { + start_sample = trak->end_sample + 1; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stss crop end_sample:%uD", start_sample); + + } else { + return; + } + + data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf; + + entries = trak->sync_samples_entries; + entry = (uint32_t *) data->pos; + end = (uint32_t *) data->last; + + while (entry < end) { + sample = ngx_mp4_get_32value(entry); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sync:%uD", sample); + + if (sample >= start_sample) { + goto found; + } + + entries--; + entry++; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sample is out of mp4 stss atom"); + +found: + + if (start) { + data->pos = (u_char *) entry; + trak->sync_samples_entries = entries; + + } else { + data->last = (u_char *) entry; + trak->sync_samples_entries -= entries; + } +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char entries[4]; +} ngx_mp4_ctts_atom_t; + +typedef struct { + u_char count[4]; + u_char offset[4]; +} ngx_mp4_ctts_entry_t; + + +static ngx_int_t +ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_table, *atom_end; + uint32_t entries; + ngx_buf_t *atom, *data; + ngx_mp4_ctts_atom_t *ctts_atom; + ngx_http_mp4_trak_t *trak; + + /* composition offsets atom */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ctts atom"); + + atom_header = ngx_mp4_atom_header(mp4); + ctts_atom = (ngx_mp4_ctts_atom_t *) atom_header; + ngx_mp4_set_atom_name(ctts_atom, 'c', 't', 't', 's'); + + if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 ctts atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + entries = ngx_mp4_get_32value(ctts_atom->entries); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "composition offset entries:%uD", entries); + + trak = ngx_mp4_last_trak(mp4); + trak->composition_offset_entries = entries; + + atom_table = atom_header + sizeof(ngx_mp4_ctts_atom_t); + + atom = &trak->ctts_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_table; + + if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) + + entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 ctts atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + atom_end = atom_table + entries * sizeof(ngx_mp4_ctts_entry_t); + + data = &trak->ctts_data_buf; + data->temporary = 1; + data->pos = atom_table; + data->last = atom_end; + + trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = atom; + trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = data; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static void +ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + size_t atom_size; + ngx_buf_t *atom, *data; + ngx_mp4_ctts_atom_t *ctts_atom; + + /* + * mdia.minf.stbl.ctts updating requires trak->start_sample + * from mdia.minf.stbl.stts which depends on value from mdia.mdhd + * atom which may reside after mdia.minf + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 ctts atom update"); + + data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf; + + if (data == NULL) { + return; + } + + ngx_http_mp4_crop_ctts_data(mp4, trak, 1); + ngx_http_mp4_crop_ctts_data(mp4, trak, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "composition offset entries:%uD", + trak->composition_offset_entries); + + if (trak->composition_offset_entries == 0) { + trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = NULL; + trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = NULL; + return; + } + + atom_size = sizeof(ngx_mp4_ctts_atom_t) + (data->last - data->pos); + trak->size += atom_size; + + atom = trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf; + ctts_atom = (ngx_mp4_ctts_atom_t *) atom->pos; + + ngx_mp4_set_32value(ctts_atom->size, atom_size); + ngx_mp4_set_32value(ctts_atom->entries, trak->composition_offset_entries); + + return; +} + + +static void +ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, ngx_uint_t start) +{ + uint32_t count, start_sample, rest; + ngx_buf_t *data; + ngx_uint_t entries; + ngx_mp4_ctts_entry_t *entry, *end; + + /* sync samples starts from 1 */ + + if (start) { + start_sample = trak->start_sample + 1; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 ctts crop start_sample:%uD", start_sample); + + } else if (mp4->length) { + start_sample = trak->end_sample - trak->start_sample + 1; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 ctts crop end_sample:%uD", start_sample); + + } else { + return; + } + + data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf; + + entries = trak->composition_offset_entries; + entry = (ngx_mp4_ctts_entry_t *) data->pos; + end = (ngx_mp4_ctts_entry_t *) data->last; + + while (entry < end) { + count = ngx_mp4_get_32value(entry->count); + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sample:%uD, count:%uD, offset:%uD", + start_sample, count, ngx_mp4_get_32value(entry->offset)); + + if (start_sample <= count) { + rest = start_sample - 1; + goto found; + } + + start_sample -= count; + entries--; + entry++; + } + + if (start) { + data->pos = (u_char *) end; + trak->composition_offset_entries = 0; + } + + return; + +found: + + if (start) { + ngx_mp4_set_32value(entry->count, count - rest); + data->pos = (u_char *) entry; + trak->composition_offset_entries = entries; + + } else { + ngx_mp4_set_32value(entry->count, rest); + data->last = (u_char *) (entry + 1); + trak->composition_offset_entries -= entries - 1; + } +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char entries[4]; +} ngx_mp4_stsc_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_table, *atom_end; + uint32_t entries; + ngx_buf_t *atom, *data; + ngx_mp4_stsc_atom_t *stsc_atom; + ngx_http_mp4_trak_t *trak; + + /* sample-to-chunk atom */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsc atom"); + + atom_header = ngx_mp4_atom_header(mp4); + stsc_atom = (ngx_mp4_stsc_atom_t *) atom_header; + ngx_mp4_set_atom_name(stsc_atom, 's', 't', 's', 'c'); + + if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stsc atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + entries = ngx_mp4_get_32value(stsc_atom->entries); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sample-to-chunk entries:%uD", entries); + + if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) + + entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stsc atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + atom_table = atom_header + sizeof(ngx_mp4_stsc_atom_t); + atom_end = atom_table + entries * sizeof(ngx_mp4_stsc_entry_t); + + trak = ngx_mp4_last_trak(mp4); + trak->sample_to_chunk_entries = entries; + + atom = &trak->stsc_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_table; + + data = &trak->stsc_data_buf; + data->temporary = 1; + data->pos = atom_table; + data->last = atom_end; + + trak->out[NGX_HTTP_MP4_STSC_ATOM].buf = atom; + trak->out[NGX_HTTP_MP4_STSC_DATA].buf = data; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + size_t atom_size; + uint32_t chunk; + ngx_buf_t *atom, *data; + ngx_mp4_stsc_atom_t *stsc_atom; + ngx_mp4_stsc_entry_t *entry, *end; + + /* + * mdia.minf.stbl.stsc updating requires trak->start_sample + * from mdia.minf.stbl.stts which depends on value from mdia.mdhd + * atom which may reside after mdia.minf + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stsc atom update"); + + data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf; + + if (data == NULL) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "no mp4 stsc atoms were found in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + if (trak->sample_to_chunk_entries == 0) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "zero number of entries in stsc atom in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + if (ngx_http_mp4_crop_stsc_data(mp4, trak, 1) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_mp4_crop_stsc_data(mp4, trak, 0) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sample-to-chunk entries:%uD", + trak->sample_to_chunk_entries); + + entry = (ngx_mp4_stsc_entry_t *) data->pos; + end = (ngx_mp4_stsc_entry_t *) data->last; + + while (entry < end) { + chunk = ngx_mp4_get_32value(entry->chunk); + chunk -= trak->start_chunk; + ngx_mp4_set_32value(entry->chunk, chunk); + entry++; + } + + atom_size = sizeof(ngx_mp4_stsc_atom_t) + + trak->sample_to_chunk_entries * sizeof(ngx_mp4_stsc_entry_t); + + trak->size += atom_size; + + atom = trak->out[NGX_HTTP_MP4_STSC_ATOM].buf; + stsc_atom = (ngx_mp4_stsc_atom_t *) atom->pos; + + ngx_mp4_set_32value(stsc_atom->size, atom_size); + ngx_mp4_set_32value(stsc_atom->entries, trak->sample_to_chunk_entries); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, ngx_uint_t start) +{ + uint32_t start_sample, chunk, samples, id, next_chunk, n, + prev_samples; + ngx_buf_t *data, *buf; + ngx_uint_t entries, target_chunk, chunk_samples; + ngx_mp4_stsc_entry_t *entry, *end, *first; + + entries = trak->sample_to_chunk_entries - 1; + + if (start) { + start_sample = (uint32_t) trak->start_sample; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stsc crop start_sample:%uD", start_sample); + + } else if (mp4->length) { + start_sample = (uint32_t) (trak->end_sample - trak->start_sample); + samples = 0; + + data = trak->out[NGX_HTTP_MP4_STSC_START].buf; + + if (data) { + entry = (ngx_mp4_stsc_entry_t *) data->pos; + samples = ngx_mp4_get_32value(entry->samples); + entries--; + + if (samples > start_sample) { + samples = start_sample; + ngx_mp4_set_32value(entry->samples, samples); + } + + start_sample -= samples; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stsc crop end_sample:%uD, ext_samples:%uD", + start_sample, samples); + + } else { + return NGX_OK; + } + + data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf; + + entry = (ngx_mp4_stsc_entry_t *) data->pos; + end = (ngx_mp4_stsc_entry_t *) data->last; + + chunk = ngx_mp4_get_32value(entry->chunk); + samples = ngx_mp4_get_32value(entry->samples); + id = ngx_mp4_get_32value(entry->id); + prev_samples = 0; + entry++; + + while (entry < end) { + + next_chunk = ngx_mp4_get_32value(entry->chunk); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sample:%uD, chunk:%uD, chunks:%uD, " + "samples:%uD, id:%uD", + start_sample, chunk, next_chunk - chunk, samples, id); + + n = (next_chunk - chunk) * samples; + + if (start_sample < n) { + goto found; + } + + start_sample -= n; + + prev_samples = samples; + chunk = next_chunk; + samples = ngx_mp4_get_32value(entry->samples); + id = ngx_mp4_get_32value(entry->id); + entries--; + entry++; + } + + next_chunk = trak->chunks + 1; + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sample:%uD, chunk:%uD, chunks:%uD, samples:%uD", + start_sample, chunk, next_chunk - chunk, samples); + + n = (next_chunk - chunk) * samples; + + if (start_sample > n) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "%s time is out mp4 stsc chunks in \"%s\"", + start ? "start" : "end", mp4->file.name.data); + return NGX_ERROR; + } + +found: + + entries++; + entry--; + + if (samples == 0) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "zero number of samples in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + target_chunk = chunk - 1; + target_chunk += start_sample / samples; + chunk_samples = start_sample % samples; + + if (start) { + data->pos = (u_char *) entry; + + trak->sample_to_chunk_entries = entries; + trak->start_chunk = target_chunk; + trak->start_chunk_samples = chunk_samples; + + ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 1); + + samples -= chunk_samples; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "start_chunk:%ui, start_chunk_samples:%ui", + trak->start_chunk, trak->start_chunk_samples); + + } else { + if (start_sample) { + data->last = (u_char *) (entry + 1); + trak->sample_to_chunk_entries -= entries - 1; + trak->end_chunk_samples = samples; + + } else { + data->last = (u_char *) entry; + trak->sample_to_chunk_entries -= entries; + trak->end_chunk_samples = prev_samples; + } + + if (chunk_samples) { + trak->end_chunk = target_chunk + 1; + trak->end_chunk_samples = chunk_samples; + + } else { + trak->end_chunk = target_chunk; + } + + samples = chunk_samples; + next_chunk = chunk + 1; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "end_chunk:%ui, end_chunk_samples:%ui", + trak->end_chunk, trak->end_chunk_samples); + } + + if (chunk_samples && next_chunk - target_chunk == 2) { + + ngx_mp4_set_32value(entry->samples, samples); + + } else if (chunk_samples && start) { + + first = &trak->stsc_start_chunk_entry; + ngx_mp4_set_32value(first->chunk, 1); + ngx_mp4_set_32value(first->samples, samples); + ngx_mp4_set_32value(first->id, id); + + buf = &trak->stsc_start_chunk_buf; + buf->temporary = 1; + buf->pos = (u_char *) first; + buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t); + + trak->out[NGX_HTTP_MP4_STSC_START].buf = buf; + + ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 2); + + trak->sample_to_chunk_entries++; + + } else if (chunk_samples) { + + first = &trak->stsc_end_chunk_entry; + ngx_mp4_set_32value(first->chunk, trak->end_chunk - trak->start_chunk); + ngx_mp4_set_32value(first->samples, samples); + ngx_mp4_set_32value(first->id, id); + + buf = &trak->stsc_end_chunk_buf; + buf->temporary = 1; + buf->pos = (u_char *) first; + buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t); + + trak->out[NGX_HTTP_MP4_STSC_END].buf = buf; + + trak->sample_to_chunk_entries++; + } + + return NGX_OK; +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char uniform_size[4]; + u_char entries[4]; +} ngx_mp4_stsz_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_table, *atom_end; + size_t atom_size; + uint32_t entries, size; + ngx_buf_t *atom, *data; + ngx_mp4_stsz_atom_t *stsz_atom; + ngx_http_mp4_trak_t *trak; + + /* sample sizes atom */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsz atom"); + + atom_header = ngx_mp4_atom_header(mp4); + stsz_atom = (ngx_mp4_stsz_atom_t *) atom_header; + ngx_mp4_set_atom_name(stsz_atom, 's', 't', 's', 'z'); + + if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stsz atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + size = ngx_mp4_get_32value(stsz_atom->uniform_size); + entries = ngx_mp4_get_32value(stsz_atom->entries); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "sample uniform size:%uD, entries:%uD", size, entries); + + trak = ngx_mp4_last_trak(mp4); + trak->sample_sizes_entries = entries; + + atom_table = atom_header + sizeof(ngx_mp4_stsz_atom_t); + + atom = &trak->stsz_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_table; + + trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf = atom; + + if (size == 0) { + if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) + + entries * sizeof(uint32_t) > atom_data_size) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stsz atom too small", + mp4->file.name.data); + return NGX_ERROR; + } + + atom_end = atom_table + entries * sizeof(uint32_t); + + data = &trak->stsz_data_buf; + data->temporary = 1; + data->pos = atom_table; + data->last = atom_end; + + trak->out[NGX_HTTP_MP4_STSZ_DATA].buf = data; + + } else { + /* if size != 0 then all samples are the same size */ + /* TODO : chunk samples */ + atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size; + ngx_mp4_set_32value(atom_header, atom_size); + trak->size += atom_size; + } + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + size_t atom_size; + uint32_t *pos, *end, entries; + ngx_buf_t *atom, *data; + ngx_mp4_stsz_atom_t *stsz_atom; + + /* + * mdia.minf.stbl.stsz updating requires trak->start_sample + * from mdia.minf.stbl.stts which depends on value from mdia.mdhd + * atom which may reside after mdia.minf + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stsz atom update"); + + data = trak->out[NGX_HTTP_MP4_STSZ_DATA].buf; + + if (data) { + entries = trak->sample_sizes_entries; + + if (trak->start_sample > entries) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "start time is out mp4 stsz samples in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + entries -= trak->start_sample; + data->pos += trak->start_sample * sizeof(uint32_t); + end = (uint32_t *) data->pos; + + for (pos = end - trak->start_chunk_samples; pos < end; pos++) { + trak->start_chunk_samples_size += ngx_mp4_get_32value(pos); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "chunk samples sizes:%uL", + trak->start_chunk_samples_size); + + if (mp4->length) { + if (trak->end_sample - trak->start_sample > entries) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "end time is out mp4 stsz samples in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + entries = trak->end_sample - trak->start_sample; + data->last = data->pos + entries * sizeof(uint32_t); + end = (uint32_t *) data->last; + + for (pos = end - trak->end_chunk_samples; pos < end; pos++) { + trak->end_chunk_samples_size += ngx_mp4_get_32value(pos); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stsz end_chunk_samples_size:%uL", + trak->end_chunk_samples_size); + } + + atom_size = sizeof(ngx_mp4_stsz_atom_t) + (data->last - data->pos); + trak->size += atom_size; + + atom = trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf; + stsz_atom = (ngx_mp4_stsz_atom_t *) atom->pos; + + ngx_mp4_set_32value(stsz_atom->size, atom_size); + ngx_mp4_set_32value(stsz_atom->entries, entries); + } + + return NGX_OK; +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char entries[4]; +} ngx_mp4_stco_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_table, *atom_end; + uint32_t entries; + ngx_buf_t *atom, *data; + ngx_mp4_stco_atom_t *stco_atom; + ngx_http_mp4_trak_t *trak; + + /* chunk offsets atom */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stco atom"); + + atom_header = ngx_mp4_atom_header(mp4); + stco_atom = (ngx_mp4_stco_atom_t *) atom_header; + ngx_mp4_set_atom_name(stco_atom, 's', 't', 'c', 'o'); + + if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stco atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + entries = ngx_mp4_get_32value(stco_atom->entries); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries); + + if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) + + entries * sizeof(uint32_t) > atom_data_size) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 stco atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + atom_table = atom_header + sizeof(ngx_mp4_stco_atom_t); + atom_end = atom_table + entries * sizeof(uint32_t); + + trak = ngx_mp4_last_trak(mp4); + trak->chunks = entries; + + atom = &trak->stco_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_table; + + data = &trak->stco_data_buf; + data->temporary = 1; + data->pos = atom_table; + data->last = atom_end; + + trak->out[NGX_HTTP_MP4_STCO_ATOM].buf = atom; + trak->out[NGX_HTTP_MP4_STCO_DATA].buf = data; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + size_t atom_size; + uint32_t entries; + ngx_buf_t *atom, *data; + ngx_mp4_stco_atom_t *stco_atom; + + /* + * mdia.minf.stbl.stco updating requires trak->start_chunk + * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd + * atom which may reside after mdia.minf + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stco atom update"); + + data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf; + + if (data == NULL) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "no mp4 stco atoms were found in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + if (trak->start_chunk > trak->chunks) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "start time is out mp4 stco chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + data->pos += trak->start_chunk * sizeof(uint32_t); + + trak->start_offset = ngx_mp4_get_32value(data->pos); + trak->start_offset += trak->start_chunk_samples_size; + ngx_mp4_set_32value(data->pos, trak->start_offset); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "start chunk offset:%O", trak->start_offset); + + if (mp4->length) { + + if (trak->end_chunk > trak->chunks) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "end time is out mp4 stco chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + entries = trak->end_chunk - trak->start_chunk; + data->last = data->pos + entries * sizeof(uint32_t); + + if (entries) { + trak->end_offset = + ngx_mp4_get_32value(data->last - sizeof(uint32_t)); + trak->end_offset += trak->end_chunk_samples_size; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "end chunk offset:%O", trak->end_offset); + } + + } else { + entries = trak->chunks - trak->start_chunk; + trak->end_offset = mp4->mdat_data.buf->file_last; + } + + if (entries == 0) { + trak->start_offset = mp4->end; + trak->end_offset = 0; + } + + atom_size = sizeof(ngx_mp4_stco_atom_t) + (data->last - data->pos); + trak->size += atom_size; + + atom = trak->out[NGX_HTTP_MP4_STCO_ATOM].buf; + stco_atom = (ngx_mp4_stco_atom_t *) atom->pos; + + ngx_mp4_set_32value(stco_atom->size, atom_size); + ngx_mp4_set_32value(stco_atom->entries, entries); + + return NGX_OK; +} + + +static void +ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, int32_t adjustment) +{ + uint32_t offset, *entry, *end; + ngx_buf_t *data; + + /* + * moov.trak.mdia.minf.stbl.stco adjustment requires + * minimal start offset of all traks and new moov atom size + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 stco atom adjustment"); + + data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf; + entry = (uint32_t *) data->pos; + end = (uint32_t *) data->last; + + while (entry < end) { + offset = ngx_mp4_get_32value(entry); + offset += adjustment; + ngx_mp4_set_32value(entry, offset); + entry++; + } +} + + +typedef struct { + u_char size[4]; + u_char name[4]; + u_char version[1]; + u_char flags[3]; + u_char entries[4]; +} ngx_mp4_co64_atom_t; + + +static ngx_int_t +ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size) +{ + u_char *atom_header, *atom_table, *atom_end; + uint32_t entries; + ngx_buf_t *atom, *data; + ngx_mp4_co64_atom_t *co64_atom; + ngx_http_mp4_trak_t *trak; + + /* chunk offsets atom */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 co64 atom"); + + atom_header = ngx_mp4_atom_header(mp4); + co64_atom = (ngx_mp4_co64_atom_t *) atom_header; + ngx_mp4_set_atom_name(co64_atom, 'c', 'o', '6', '4'); + + if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) > atom_data_size) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 co64 atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + entries = ngx_mp4_get_32value(co64_atom->entries); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries); + + if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) + + entries * sizeof(uint64_t) > atom_data_size) + { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "\"%s\" mp4 co64 atom too small", mp4->file.name.data); + return NGX_ERROR; + } + + atom_table = atom_header + sizeof(ngx_mp4_co64_atom_t); + atom_end = atom_table + entries * sizeof(uint64_t); + + trak = ngx_mp4_last_trak(mp4); + trak->chunks = entries; + + atom = &trak->co64_atom_buf; + atom->temporary = 1; + atom->pos = atom_header; + atom->last = atom_table; + + data = &trak->co64_data_buf; + data->temporary = 1; + data->pos = atom_table; + data->last = atom_end; + + trak->out[NGX_HTTP_MP4_CO64_ATOM].buf = atom; + trak->out[NGX_HTTP_MP4_CO64_DATA].buf = data; + + ngx_mp4_atom_next(mp4, atom_data_size); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak) +{ + size_t atom_size; + uint64_t entries; + ngx_buf_t *atom, *data; + ngx_mp4_co64_atom_t *co64_atom; + + /* + * mdia.minf.stbl.co64 updating requires trak->start_chunk + * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd + * atom which may reside after mdia.minf + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 co64 atom update"); + + data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf; + + if (data == NULL) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "no mp4 co64 atoms were found in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + if (trak->start_chunk > trak->chunks) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "start time is out mp4 co64 chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + data->pos += trak->start_chunk * sizeof(uint64_t); + + trak->start_offset = ngx_mp4_get_64value(data->pos); + trak->start_offset += trak->start_chunk_samples_size; + ngx_mp4_set_64value(data->pos, trak->start_offset); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "start chunk offset:%O", trak->start_offset); + + if (mp4->length) { + + if (trak->end_chunk > trak->chunks) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "end time is out mp4 co64 chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + + entries = trak->end_chunk - trak->start_chunk; + data->last = data->pos + entries * sizeof(uint64_t); + + if (entries) { + trak->end_offset = + ngx_mp4_get_64value(data->last - sizeof(uint64_t)); + trak->end_offset += trak->end_chunk_samples_size; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "end chunk offset:%O", trak->end_offset); + } + + } else { + entries = trak->chunks - trak->start_chunk; + trak->end_offset = mp4->mdat_data.buf->file_last; + } + + if (entries == 0) { + trak->start_offset = mp4->end; + trak->end_offset = 0; + } + + atom_size = sizeof(ngx_mp4_co64_atom_t) + (data->last - data->pos); + trak->size += atom_size; + + atom = trak->out[NGX_HTTP_MP4_CO64_ATOM].buf; + co64_atom = (ngx_mp4_co64_atom_t *) atom->pos; + + ngx_mp4_set_32value(co64_atom->size, atom_size); + ngx_mp4_set_32value(co64_atom->entries, entries); + + return NGX_OK; +} + + +static void +ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4, + ngx_http_mp4_trak_t *trak, off_t adjustment) +{ + uint64_t offset, *entry, *end; + ngx_buf_t *data; + + /* + * moov.trak.mdia.minf.stbl.co64 adjustment requires + * minimal start offset of all traks and new moov atom size + */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, + "mp4 co64 atom adjustment"); + + data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf; + entry = (uint64_t *) data->pos; + end = (uint64_t *) data->last; + + while (entry < end) { + offset = ngx_mp4_get_64value(entry); + offset += adjustment; + ngx_mp4_set_64value(entry, offset); + entry++; + } +} + + +static char * +ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_core_loc_conf_t *clcf; + + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); + clcf->handler = ngx_http_mp4_handler; + + return NGX_CONF_OK; +} + + +static void * +ngx_http_mp4_create_conf(ngx_conf_t *cf) +{ + ngx_http_mp4_conf_t *conf; + + conf = ngx_palloc(cf->pool, sizeof(ngx_http_mp4_conf_t)); + if (conf == NULL) { + return NULL; + } + + conf->buffer_size = NGX_CONF_UNSET_SIZE; + conf->max_buffer_size = NGX_CONF_UNSET_SIZE; + + return conf; +} + + +static char * +ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_mp4_conf_t *prev = parent; + ngx_http_mp4_conf_t *conf = child; + + ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 512 * 1024); + ngx_conf_merge_size_value(conf->max_buffer_size, prev->max_buffer_size, + 10 * 1024 * 1024); + + return NGX_CONF_OK; +}