Mercurial > hg > nginx
diff src/http/ngx_http_upstream.c @ 9021:8d0753760546 quic
Merged with the default branch.
author | Sergey Kandaurov <pluknet@nginx.com> |
---|---|
date | Wed, 22 Jun 2022 18:34:58 +0400 |
parents | 5c86189a1c1b c7e25324be11 |
children | e88cdaa0f1ff |
line wrap: on
line diff
--- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -101,6 +101,9 @@ static void ngx_http_upstream_finalize_r static ngx_int_t ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t + ngx_http_upstream_process_multi_header_lines(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_content_length(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_last_modified(ngx_http_request_t *r, @@ -147,11 +150,6 @@ static ngx_int_t ngx_http_upstream_rewri static ngx_int_t ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); -#if (NGX_HTTP_GZIP) -static ngx_int_t ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, - ngx_table_elt_t *h, ngx_uint_t offset); -#endif - static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_upstream_addr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); @@ -231,7 +229,7 @@ static ngx_http_upstream_header_t ngx_h offsetof(ngx_http_headers_out_t, server), 0 }, { ngx_string("WWW-Authenticate"), - ngx_http_upstream_process_header_line, + ngx_http_upstream_process_multi_header_lines, offsetof(ngx_http_upstream_headers_in_t, www_authenticate), ngx_http_upstream_copy_header_line, 0, 0 }, @@ -241,12 +239,13 @@ static ngx_http_upstream_header_t ngx_h ngx_http_upstream_rewrite_location, 0, 0 }, { ngx_string("Refresh"), - ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, refresh), ngx_http_upstream_rewrite_refresh, 0, 0 }, { ngx_string("Set-Cookie"), ngx_http_upstream_process_set_cookie, - offsetof(ngx_http_upstream_headers_in_t, cookies), + offsetof(ngx_http_upstream_headers_in_t, set_cookie), ngx_http_upstream_rewrite_set_cookie, 0, 1 }, { ngx_string("Content-Disposition"), @@ -264,8 +263,7 @@ static ngx_http_upstream_header_t ngx_h offsetof(ngx_http_headers_out_t, expires), 1 }, { ngx_string("Accept-Ranges"), - ngx_http_upstream_process_header_line, - offsetof(ngx_http_upstream_headers_in_t, accept_ranges), + ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_copy_allow_ranges, offsetof(ngx_http_headers_out_t, accept_ranges), 1 }, @@ -316,12 +314,10 @@ static ngx_http_upstream_header_t ngx_h ngx_http_upstream_process_transfer_encoding, 0, ngx_http_upstream_ignore_header_line, 0, 0 }, -#if (NGX_HTTP_GZIP) { ngx_string("Content-Encoding"), - ngx_http_upstream_process_header_line, - offsetof(ngx_http_upstream_headers_in_t, content_encoding), - ngx_http_upstream_copy_content_encoding, 0, 0 }, -#endif + ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_copy_header_line, + offsetof(ngx_http_headers_out_t, content_encoding), 0 }, { ngx_null_string, NULL, 0, NULL, 0, 0 } }; @@ -1714,8 +1710,10 @@ ngx_http_upstream_ssl_init_connection(ng } } - if (u->conf->ssl_certificate && (u->conf->ssl_certificate->lengths - || u->conf->ssl_certificate_key->lengths)) + if (u->conf->ssl_certificate + && u->conf->ssl_certificate->value.len + && (u->conf->ssl_certificate->lengths + || u->conf->ssl_certificate_key->lengths)) { if (ngx_http_upstream_ssl_certificate(r, u, c) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, @@ -2671,7 +2669,7 @@ ngx_http_upstream_intercept_errors(ngx_h { ngx_int_t status; ngx_uint_t i; - ngx_table_elt_t *h; + ngx_table_elt_t *h, *ho, **ph; ngx_http_err_page_t *err_page; ngx_http_core_loc_conf_t *clcf; @@ -2700,23 +2698,36 @@ ngx_http_upstream_intercept_errors(ngx_h if (status == NGX_HTTP_UNAUTHORIZED && u->headers_in.www_authenticate) { - h = ngx_list_push(&r->headers_out.headers); - - if (h == NULL) { - ngx_http_upstream_finalize_request(r, u, + h = u->headers_in.www_authenticate; + ph = &r->headers_out.www_authenticate; + + while (h) { + ho = ngx_list_push(&r->headers_out.headers); + + if (ho == NULL) { + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_OK; + return NGX_OK; + } + + *ho = *h; + ho->next = NULL; + + *ph = ho; + ph = &ho->next; + + h = h->next; } - - *h = *u->headers_in.www_authenticate; - - r->headers_out.www_authenticate = h; } #if (NGX_HTTP_CACHE) if (r->cache) { + if (u->headers_in.no_cache || u->headers_in.expired) { + u->cacheable = 0; + } + if (u->cacheable) { time_t valid; @@ -2811,6 +2822,10 @@ ngx_http_upstream_process_headers(ngx_ht umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + if (u->headers_in.no_cache || u->headers_in.expired) { + u->cacheable = 0; + } + if (u->headers_in.x_accel_redirect && !(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT)) { @@ -2831,6 +2846,10 @@ ngx_http_upstream_process_headers(ngx_ht i = 0; } + if (h[i].hash == 0) { + continue; + } + hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash, h[i].lowcase_key, h[i].key.len); @@ -2884,6 +2903,10 @@ ngx_http_upstream_process_headers(ngx_ht i = 0; } + if (h[i].hash == 0) { + continue; + } + if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, h[i].lowcase_key, h[i].key.len)) { @@ -4635,9 +4658,35 @@ ngx_http_upstream_process_header_line(ng ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset); - if (*ph == NULL) { - *ph = h; - } + if (*ph) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &(*ph)->key, &(*ph)->value); + h->hash = 0; + return NGX_OK; + } + + *ph = h; + h->next = NULL; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_multi_header_lines(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_table_elt_t **ph; + + ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset); + + while (*ph) { ph = &(*ph)->next; } + + *ph = h; + h->next = NULL; return NGX_OK; } @@ -4659,9 +4708,34 @@ ngx_http_upstream_process_content_length u = r->upstream; + if (u->headers_in.content_length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\"", + &h->key, &h->value, + &u->headers_in.content_length->key, + &u->headers_in.content_length->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (u->headers_in.transfer_encoding) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent \"Content-Length\" and " + "\"Transfer-Encoding\" headers at the same time"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + h->next = NULL; u->headers_in.content_length = h; u->headers_in.content_length_n = ngx_atoof(h->value.data, h->value.len); + if (u->headers_in.content_length_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid \"Content-Length\" header: " + "\"%V: %V\"", &h->key, &h->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + return NGX_OK; } @@ -4674,6 +4748,18 @@ ngx_http_upstream_process_last_modified( u = r->upstream; + if (u->headers_in.last_modified) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.last_modified->key, + &u->headers_in.last_modified->value); + h->hash = 0; + return NGX_OK; + } + + h->next = NULL; u->headers_in.last_modified = h; u->headers_in.last_modified_time = ngx_parse_http_time(h->value.data, h->value.len); @@ -4686,26 +4772,16 @@ static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - ngx_array_t *pa; ngx_table_elt_t **ph; ngx_http_upstream_t *u; u = r->upstream; - pa = &u->headers_in.cookies; - - if (pa->elts == NULL) { - if (ngx_array_init(pa, r->pool, 1, sizeof(ngx_table_elt_t *)) != NGX_OK) - { - return NGX_ERROR; - } - } - - ph = ngx_array_push(pa); - if (ph == NULL) { - return NGX_ERROR; - } + ph = &u->headers_in.set_cookie; + + while (*ph) { ph = &(*ph)->next; } *ph = h; + h->next = NULL; #if (NGX_HTTP_CACHE) if (!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_SET_COOKIE)) { @@ -4721,26 +4797,16 @@ static ngx_int_t ngx_http_upstream_process_cache_control(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - ngx_array_t *pa; - ngx_table_elt_t **ph; - ngx_http_upstream_t *u; + ngx_table_elt_t **ph; + ngx_http_upstream_t *u; u = r->upstream; - pa = &u->headers_in.cache_control; - - if (pa->elts == NULL) { - if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) - { - return NGX_ERROR; - } - } - - ph = ngx_array_push(pa); - if (ph == NULL) { - return NGX_ERROR; - } + ph = &u->headers_in.cache_control; + + while (*ph) { ph = &(*ph)->next; } *ph = h; + h->next = NULL; #if (NGX_HTTP_CACHE) { @@ -4755,18 +4821,18 @@ ngx_http_upstream_process_cache_control( return NGX_OK; } - if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) { - return NGX_OK; - } - start = h->value.data; last = start + h->value.len; + if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) { + goto extensions; + } + if (ngx_strlcasestrn(start, last, (u_char *) "no-cache", 8 - 1) != NULL || ngx_strlcasestrn(start, last, (u_char *) "no-store", 8 - 1) != NULL || ngx_strlcasestrn(start, last, (u_char *) "private", 7 - 1) != NULL) { - u->cacheable = 0; + u->headers_in.no_cache = 1; return NGX_OK; } @@ -4796,12 +4862,15 @@ ngx_http_upstream_process_cache_control( } if (n == 0) { - u->cacheable = 0; + u->headers_in.no_cache = 1; return NGX_OK; } r->cache->valid_sec = ngx_time() + n; - } + u->headers_in.expired = 0; + } + +extensions: p = ngx_strlcasestrn(start, last, (u_char *) "stale-while-revalidate=", 23 - 1); @@ -4862,7 +4931,20 @@ ngx_http_upstream_process_expires(ngx_ht ngx_http_upstream_t *u; u = r->upstream; + + if (u->headers_in.expires) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.expires->key, + &u->headers_in.expires->value); + h->hash = 0; + return NGX_OK; + } + u->headers_in.expires = h; + h->next = NULL; #if (NGX_HTTP_CACHE) { @@ -4883,7 +4965,7 @@ ngx_http_upstream_process_expires(ngx_ht expires = ngx_parse_http_time(h->value.data, h->value.len); if (expires == NGX_ERROR || expires < ngx_time()) { - u->cacheable = 0; + u->headers_in.expired = 1; return NGX_OK; } @@ -4902,7 +4984,20 @@ ngx_http_upstream_process_accel_expires( ngx_http_upstream_t *u; u = r->upstream; + + if (u->headers_in.x_accel_expires) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.x_accel_expires->key, + &u->headers_in.x_accel_expires->value); + h->hash = 0; + return NGX_OK; + } + u->headers_in.x_accel_expires = h; + h->next = NULL; #if (NGX_HTTP_CACHE) { @@ -4934,6 +5029,8 @@ ngx_http_upstream_process_accel_expires( default: r->cache->valid_sec = ngx_time() + n; + u->headers_in.no_cache = 0; + u->headers_in.expired = 0; return NGX_OK; } } @@ -4945,6 +5042,8 @@ ngx_http_upstream_process_accel_expires( if (n != NGX_ERROR) { r->cache->valid_sec = n; + u->headers_in.no_cache = 0; + u->headers_in.expired = 0; } } #endif @@ -4961,7 +5060,20 @@ ngx_http_upstream_process_limit_rate(ngx ngx_http_upstream_t *u; u = r->upstream; + + if (u->headers_in.x_accel_limit_rate) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.x_accel_limit_rate->key, + &u->headers_in.x_accel_limit_rate->value); + h->hash = 0; + return NGX_OK; + } + u->headers_in.x_accel_limit_rate = h; + h->next = NULL; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE) { return NGX_OK; @@ -5020,7 +5132,11 @@ static ngx_int_t ngx_http_upstream_process_charset(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - if (r->upstream->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_CHARSET) { + ngx_http_upstream_t *u; + + u = r->upstream; + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_CHARSET) { return NGX_OK; } @@ -5034,13 +5150,22 @@ static ngx_int_t ngx_http_upstream_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - r->upstream->headers_in.connection = h; + ngx_table_elt_t **ph; + ngx_http_upstream_t *u; + + u = r->upstream; + ph = &u->headers_in.connection; + + while (*ph) { ph = &(*ph)->next; } + + *ph = h; + h->next = NULL; if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, (u_char *) "close", 5 - 1) != NULL) { - r->upstream->headers_in.connection_close = 1; + u->headers_in.connection_close = 1; } return NGX_OK; @@ -5051,13 +5176,40 @@ static ngx_int_t ngx_http_upstream_process_transfer_encoding(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - r->upstream->headers_in.transfer_encoding = h; - - if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, - (u_char *) "chunked", 7 - 1) - != NULL) + ngx_http_upstream_t *u; + + u = r->upstream; + + if (u->headers_in.transfer_encoding) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\"", + &h->key, &h->value, + &u->headers_in.transfer_encoding->key, + &u->headers_in.transfer_encoding->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (u->headers_in.content_length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent \"Content-Length\" and " + "\"Transfer-Encoding\" headers at the same time"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + u->headers_in.transfer_encoding = h; + h->next = NULL; + + if (h->value.len == 7 + && ngx_strncasecmp(h->value.data, (u_char *) "chunked", 7) == 0) { - r->upstream->headers_in.chunked = 1; + u->headers_in.chunked = 1; + + } else { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unknown \"Transfer-Encoding\": \"%V\"", + &h->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; } return NGX_OK; @@ -5068,29 +5220,74 @@ static ngx_int_t ngx_http_upstream_process_vary(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - ngx_http_upstream_t *u; + ngx_table_elt_t **ph; + ngx_http_upstream_t *u; u = r->upstream; - u->headers_in.vary = h; + ph = &u->headers_in.vary; + + while (*ph) { ph = &(*ph)->next; } + + *ph = h; + h->next = NULL; #if (NGX_HTTP_CACHE) + { + u_char *p; + size_t len; + ngx_str_t vary; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_VARY) { return NGX_OK; } - if (r->cache == NULL) { + if (r->cache == NULL || !u->cacheable) { + return NGX_OK; + } + + if (h->value.len == 1 && h->value.data[0] == '*') { + u->cacheable = 0; return NGX_OK; } - if (h->value.len > NGX_HTTP_CACHE_VARY_LEN - || (h->value.len == 1 && h->value.data[0] == '*')) - { + if (u->headers_in.vary->next) { + + len = 0; + + for (h = u->headers_in.vary; h; h = h->next) { + len += h->value.len + 2; + } + + len -= 2; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + vary.len = len; + vary.data = p; + + for (h = u->headers_in.vary; h; h = h->next) { + p = ngx_copy(p, h->value.data, h->value.len); + + if (h->next == NULL) { + break; + } + + *p++ = ','; *p++ = ' '; + } + + } else { + vary = h->value; + } + + if (vary.len > NGX_HTTP_CACHE_VARY_LEN) { u->cacheable = 0; } - r->cache->vary = h->value; - + r->cache->vary = vary; + } #endif return NGX_OK; @@ -5113,6 +5310,7 @@ ngx_http_upstream_copy_header_line(ngx_h if (offset) { ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset); *ph = ho; + ho->next = NULL; } return NGX_OK; @@ -5123,18 +5321,8 @@ static ngx_int_t ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - ngx_array_t *pa; ngx_table_elt_t *ho, **ph; - pa = (ngx_array_t *) ((char *) &r->headers_out + offset); - - if (pa->elts == NULL) { - if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) - { - return NGX_ERROR; - } - } - ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; @@ -5142,12 +5330,12 @@ ngx_http_upstream_copy_multi_header_line *ho = *h; - ph = ngx_array_push(pa); - if (ph == NULL) { - return NGX_ERROR; - } + ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset); + + while (*ph) { ph = &(*ph)->next; } *ph = ho; + ho->next = NULL; return NGX_OK; } @@ -5217,6 +5405,7 @@ ngx_http_upstream_copy_last_modified(ngx } *ho = *h; + ho->next = NULL; r->headers_out.last_modified = ho; r->headers_out.last_modified_time = @@ -5239,6 +5428,7 @@ ngx_http_upstream_rewrite_location(ngx_h } *ho = *h; + ho->next = NULL; if (r->upstream->rewrite_redirect) { rc = r->upstream->rewrite_redirect(r, ho, 0); @@ -5284,6 +5474,7 @@ ngx_http_upstream_rewrite_refresh(ngx_ht } *ho = *h; + ho->next = NULL; if (r->upstream->rewrite_redirect) { @@ -5329,6 +5520,7 @@ ngx_http_upstream_rewrite_set_cookie(ngx } *ho = *h; + ho->next = NULL; if (r->upstream->rewrite_cookie) { rc = r->upstream->rewrite_cookie(r, ho); @@ -5382,6 +5574,7 @@ ngx_http_upstream_copy_allow_ranges(ngx_ } *ho = *h; + ho->next = NULL; r->headers_out.accept_ranges = ho; @@ -5389,29 +5582,6 @@ ngx_http_upstream_copy_allow_ranges(ngx_ } -#if (NGX_HTTP_GZIP) - -static ngx_int_t -ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, - ngx_table_elt_t *h, ngx_uint_t offset) -{ - ngx_table_elt_t *ho; - - ho = ngx_list_push(&r->headers_out.headers); - if (ho == NULL) { - return NGX_ERROR; - } - - *ho = *h; - - r->headers_out.content_encoding = ho; - - return NGX_OK; -} - -#endif - - static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf) { @@ -5723,7 +5893,7 @@ ngx_http_upstream_header_variable(ngx_ht return NGX_OK; } - return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->upstream->headers_in.headers.part, sizeof("upstream_http_") - 1); } @@ -5738,7 +5908,7 @@ ngx_http_upstream_trailer_variable(ngx_h return NGX_OK; } - return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->upstream->headers_in.trailers.part, sizeof("upstream_trailer_") - 1); } @@ -5760,9 +5930,9 @@ ngx_http_upstream_cookie_variable(ngx_ht s.len = name->len - (sizeof("upstream_cookie_") - 1); s.data = name->data + sizeof("upstream_cookie_") - 1; - if (ngx_http_parse_set_cookie_lines(&r->upstream->headers_in.cookies, + if (ngx_http_parse_set_cookie_lines(r, r->upstream->headers_in.set_cookie, &s, &cookie) - == NGX_DECLINED) + == NULL) { v->not_found = 1; return NGX_OK;