| /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de) |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <apr_strings.h> |
| #include <httpd.h> |
| #include <http_core.h> |
| #include <http_connection.h> |
| #include <http_protocol.h> |
| #include <http_log.h> |
| |
| #include "h2_private.h" |
| #include "h2_alt_svc.h" |
| #include "h2_ctx.h" |
| #include "h2_config.h" |
| #include "h2_h2.h" |
| #include "h2_util.h" |
| |
| static int h2_alt_svc_handler(request_rec *r); |
| |
| void h2_alt_svc_register_hooks(void) |
| { |
| ap_hook_post_read_request(h2_alt_svc_handler, NULL, NULL, APR_HOOK_MIDDLE); |
| } |
| |
| /** |
| * Parse an Alt-Svc specifier as described in "HTTP Alternative Services" |
| * (https://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-04) |
| * with the following changes: |
| * - do not percent encode token values |
| * - do not use quotation marks |
| */ |
| h2_alt_svc *h2_alt_svc_parse(const char *s, apr_pool_t *pool) |
| { |
| const char *sep = ap_strchr_c(s, '='); |
| if (sep) { |
| const char *alpn = apr_pstrmemdup(pool, s, sep - s); |
| const char *host = NULL; |
| int port = 0; |
| s = sep + 1; |
| sep = ap_strchr_c(s, ':'); /* mandatory : */ |
| if (sep) { |
| if (sep != s) { /* optional host */ |
| host = apr_pstrmemdup(pool, s, sep - s); |
| } |
| s = sep + 1; |
| if (*s) { /* must be a port number */ |
| port = (int)apr_atoi64(s); |
| if (port > 0 && port < (0x1 << 16)) { |
| h2_alt_svc *as = apr_pcalloc(pool, sizeof(*as)); |
| as->alpn = alpn; |
| as->host = host; |
| as->port = port; |
| return as; |
| } |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| #define h2_alt_svc_IDX(list, i) ((h2_alt_svc**)(list)->elts)[i] |
| |
| static int h2_alt_svc_handler(request_rec *r) |
| { |
| const h2_config *cfg; |
| int i; |
| |
| if (r->connection->keepalives > 0) { |
| /* Only announce Alt-Svc on the first response */ |
| return DECLINED; |
| } |
| |
| if (h2_ctx_rget(r)) { |
| return DECLINED; |
| } |
| |
| cfg = h2_config_sget(r->server); |
| if (r->hostname && cfg && cfg->alt_svcs && cfg->alt_svcs->nelts > 0) { |
| const char *alt_svc_used = apr_table_get(r->headers_in, "Alt-Svc-Used"); |
| if (!alt_svc_used) { |
| /* We have alt-svcs defined and client is not already using |
| * one, announce the services that were configured and match. |
| * The security of this connection determines if we allow |
| * other host names or ports only. |
| */ |
| const char *alt_svc = ""; |
| const char *svc_ma = ""; |
| int secure = h2_h2_is_tls(r->connection); |
| int ma = h2_config_geti(cfg, H2_CONF_ALT_SVC_MAX_AGE); |
| if (ma >= 0) { |
| svc_ma = apr_psprintf(r->pool, "; ma=%d", ma); |
| } |
| ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03043) |
| "h2_alt_svc: announce %s for %s:%d", |
| (secure? "secure" : "insecure"), |
| r->hostname, (int)r->server->port); |
| for (i = 0; i < cfg->alt_svcs->nelts; ++i) { |
| h2_alt_svc *as = h2_alt_svc_IDX(cfg->alt_svcs, i); |
| const char *ahost = as->host; |
| if (ahost && !apr_strnatcasecmp(ahost, r->hostname)) { |
| ahost = NULL; |
| } |
| if (secure || !ahost) { |
| alt_svc = apr_psprintf(r->pool, "%s%s%s=\"%s:%d\"%s", |
| alt_svc, |
| (*alt_svc? ", " : ""), as->alpn, |
| ahost? ahost : "", as->port, |
| svc_ma); |
| } |
| } |
| if (*alt_svc) { |
| apr_table_setn(r->headers_out, "Alt-Svc", alt_svc); |
| } |
| } |
| } |
| |
| return DECLINED; |
| } |