/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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_encode.h"/* H2_USE_WEBSOCKETS is conditional on APR 1.7+ */
static ap_filter_rec_t *c2_ws_out_filter_handle;
struct ws_filter_ctx { constchar *ws_accept_base64; int has_final_response; int override_body;
};
/** * Generate the "Sec-WebSocket-Accept" header field for the given key * (base64 encoded) as defined in RFC 6455 ch. 4.2.2 step 5.3
*/ staticconstchar *gen_ws_accept(conn_rec *c, constchar *key_base64)
{
apr_byte_t dgst[APR_SHA1_DIGESTSIZE]; constchar ws_guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
apr_sha1_ctx_t sha1_ctx;
if (!conn_ctx || !req->protocol || strcmp("websocket", req->protocol)) return req;
if (ap_cstr_casecmp("CONNECT", req->method)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2, "h2_c2(%s-%d): websocket request with method %s",
conn_ctx->id, conn_ctx->stream_id, req->method); return req;
} if (!req->scheme) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2, "h2_c2(%s-%d): websocket CONNECT without :scheme",
conn_ctx->id, conn_ctx->stream_id); return req;
} if (!req->path) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2, "h2_c2(%s-%d): websocket CONNECT without :path",
conn_ctx->id, conn_ctx->stream_id); return req;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c2, "h2_c2(%s-%d): websocket CONNECT for %s",
conn_ctx->id, conn_ctx->stream_id, req->path); /* Transform the HTTP/2 extended CONNECT to an internal GET using
* the HTTP/1.1 version of websocket connection setup. */
wsreq = h2_request_clone(c2->pool, req);
wsreq->method = "GET";
wsreq->protocol = NULL;
apr_table_set(wsreq->headers, "Upgrade", "websocket");
apr_table_add(wsreq->headers, "Connection", "Upgrade"); /* add Sec-WebSocket-Key header */
rv = apr_generate_random_bytes(key_raw, sizeof(key_raw)); if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, NULL, APLOGNO(10461) "error generating secret"); return NULL;
}
key_base64 = apr_pencode_base64_binary(c2->pool, key_raw, sizeof(key_raw),
APR_ENCODE_NONE, NULL);
apr_table_set(wsreq->headers, "Sec-WebSocket-Key", key_base64); /* This is now the request to process internally */
/* When this request gets processed and delivers a 101 response, * we expect it to carry a "Sec-WebSocket-Accept" header with
* exactly the following value, as per RFC 6455. */
accept_base64 = gen_ws_accept(c2, key_base64); /* Add an output filter that intercepts generated responses: * - if a valid WebSocket negotiation happens, transform the * 101 response to a 200 * - if a 2xx response happens, that does not pass the Accept test, * return a 502 indicating that the URI seems not support the websocket * protocol (RFC 8441 does not define this, but it seems the best * choice) * - if a 3xx, 4xx or 5xx response happens, forward this unchanged.
*/
ws_ctx = apr_pcalloc(c2->pool, sizeof(*ws_ctx));
ws_ctx->ws_accept_base64 = accept_base64; /* insert our filter just before the C2 core filter */
ap_remove_output_filter_byhandle(c2->output_filters, "H2_C2_NET_OUT");
ap_add_output_filter("H2_C2_WS_OUT", ws_ctx, NULL, c2);
ap_add_output_filter("H2_C2_NET_OUT", NULL, NULL, c2); /* Mark the connection as being an Upgrade, with some special handling
* since the request needs an EOS, without the stream being closed */
conn_ctx->is_upgrade = 1;
ap_assert(conn_ctx); if (ws_ctx->override_body) { /* We have overridden the original response and also its body. * If this filter is called again, we signal a hard abort to
* allow processing to terminate at the earliest. */
f->c->aborted = 1; return APR_ECONNABORTED;
}
/* Inspect the brigade, looking for RESPONSE/HEADER buckets. * Remember, this filter is only active for client websocket CONNECT * requests that we translated to an internal GET with websocket * headers. * We inspect the repsone to see if the internal resource actually * agrees to talk websocket or is "just" a normal HTTP resource that
* ignored the websocket request headers. */ for (b = APR_BRIGADE_FIRST(bb);
b != APR_BRIGADE_SENTINEL(bb);
b = bnext)
{
bnext = APR_BUCKET_NEXT(b); if (APR_BUCKET_IS_METADATA(b)) { #if AP_HAS_RESPONSE_BUCKETS if (AP_BUCKET_IS_RESPONSE(b)) { #else if (H2_BUCKET_IS_HEADERS(b)) { #endif/* !AP_HAS_RESPONSE_BUCKETS */
ws_handle_resp(f->c, conn_ctx, ws_ctx, b); continue;
}
} elseif (ws_ctx->override_body) {
apr_bucket_delete(b);
}
} return ap_pass_brigade(f->next, bb);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.